config cleanup
This commit is contained in:
32
README.md
32
README.md
@@ -154,23 +154,32 @@ Current native test coverage includes:
|
||||
"oscBindAddress": "127.0.0.1",
|
||||
"oscPort": 9000,
|
||||
"oscSmoothing": 0.18,
|
||||
"inputVideoFormat": "1080p",
|
||||
"inputFrameRate": "59.94",
|
||||
"outputVideoFormat": "1080p",
|
||||
"outputFrameRate": "59.94",
|
||||
"videoInputBackend": "decklink",
|
||||
"videoOutputBackend": "decklink",
|
||||
"input": {
|
||||
"backend": "decklink",
|
||||
"device": "default",
|
||||
"resolution": "1080p",
|
||||
"frameRate": "59.94"
|
||||
},
|
||||
"output": {
|
||||
"backend": "decklink",
|
||||
"device": "default",
|
||||
"resolution": "1080p",
|
||||
"frameRate": "59.94",
|
||||
"keying": {
|
||||
"external": true,
|
||||
"alphaRequired": false
|
||||
}
|
||||
},
|
||||
"autoReload": true,
|
||||
"maxTemporalHistoryFrames": 12,
|
||||
"enableExternalKeying": true
|
||||
"maxTemporalHistoryFrames": 12
|
||||
}
|
||||
```
|
||||
|
||||
`videoInputBackend` and `videoOutputBackend` select the concrete video I/O backend. Today the app supports `decklink` and `none`; future backends such as NDI, Spout, or file playback can be added behind the same factory boundary.
|
||||
`input.backend` and `output.backend` select the concrete video I/O backend. Today the app supports `decklink` and `none`; future backends such as NDI, Spout, or file playback can be added behind the same factory boundary. `device` is currently accepted as a backend-neutral selector placeholder; DeckLink still chooses the first compatible device.
|
||||
|
||||
`inputVideoFormat`/`inputFrameRate` select the video capture mode. `outputVideoFormat`/`outputFrameRate` select the playout mode through a backend-neutral mode description; the current DeckLink backend maps that mode to a `BMDDisplayMode` at the DeckLink boundary. Supported modes still depend on the installed card and driver. The shader stack runs at input resolution and the final rendered frame is scaled once into the configured output mode. Common examples include `720p`/`50`, `720p`/`59.94`, `1080i`/`50`, `1080i`/`59.94`, `1080p`/`25`, `1080p`/`50`, `1080p`/`59.94`, and `2160p`/`59.94`.
|
||||
`input.resolution`/`input.frameRate` select the video capture mode. `output.resolution`/`output.frameRate` select the playout mode through a backend-neutral mode description; the current DeckLink backend maps that mode to a `BMDDisplayMode` at the DeckLink boundary. Supported modes still depend on the installed card and driver. The shader stack runs at input resolution and the final rendered frame is scaled once into the configured output mode. Common examples include `720p`/`50`, `720p`/`59.94`, `1080i`/`50`, `1080i`/`59.94`, `1080p`/`25`, `1080p`/`50`, `1080p`/`59.94`, and `2160p`/`59.94`.
|
||||
|
||||
Legacy `videoFormat` and `frameRate` keys are still accepted and apply to both input and output unless the explicit input/output keys are present.
|
||||
The checked-in config uses the nested `input` and `output` objects as the supported shape.
|
||||
|
||||
The control UI is available at:
|
||||
|
||||
@@ -290,7 +299,6 @@ If `SLANG_ROOT` or `MSDF_ATLAS_GEN_ROOT` is not set, the workflow falls back to
|
||||
- Audio.
|
||||
- Genlock.
|
||||
- Logs.
|
||||
- Add more video I/O backends now that the DeckLink path is isolated under `src/video/decklink/`.
|
||||
- Support a separate sound shader `.slang` file in shader packages. (https://www.shadertoy.com/view/XsBXWt)
|
||||
- Add WebView2 for an embedded native control surface.
|
||||
- More shader-library organisation and filtering as the built-in library grows.
|
||||
|
||||
@@ -4,15 +4,24 @@
|
||||
"oscBindAddress": "0.0.0.0",
|
||||
"oscPort": 9000,
|
||||
"oscSmoothing": 0.18,
|
||||
"inputVideoFormat": "1080p",
|
||||
"inputFrameRate": "59.94",
|
||||
"outputVideoFormat": "1080p",
|
||||
"outputFrameRate": "59.94",
|
||||
"videoInputBackend": "decklink",
|
||||
"videoOutputBackend": "decklink",
|
||||
"input": {
|
||||
"backend": "decklink",
|
||||
"device": "default",
|
||||
"resolution": "1080p",
|
||||
"frameRate": "59.94"
|
||||
},
|
||||
"output": {
|
||||
"backend": "decklink",
|
||||
"device": "default",
|
||||
"resolution": "1080p",
|
||||
"frameRate": "59.94",
|
||||
"keying": {
|
||||
"external": true,
|
||||
"alphaRequired": false
|
||||
}
|
||||
},
|
||||
"autoReload": true,
|
||||
"maxTemporalHistoryFrames": 12,
|
||||
"previewEnabled": true,
|
||||
"previewFps": 59.94,
|
||||
"enableExternalKeying": true
|
||||
"previewFps": 59.94
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ When a runtime shader build completes, the app publishes a render-layer artifact
|
||||
|
||||
## Video And Preview
|
||||
|
||||
Video input and output are optional edges. `videoInputBackend` and `videoOutputBackend` select the concrete backend through the app-side backend factory. DeckLink is the current concrete backend, and `none` disables that edge. Configured video modes are represented in `src/video/core` and translated to DeckLink display modes only inside `src/video/decklink`.
|
||||
Video input and output are optional edges. `input.backend` and `output.backend` select the concrete backend through the app-side backend factory. DeckLink is the current concrete backend, and `none` disables that edge. `input` and `output` also carry the device selector plus resolution/frame-rate settings. Configured video modes are represented in `src/video/core` and translated to DeckLink display modes only inside `src/video/decklink`.
|
||||
|
||||
The input edge writes CPU frames into `InputFrameMailbox`. The current DeckLink backend captures BGRA8 directly where possible, or raw UYVY8 for render-thread GPU decode. The input edge does not call GL, render, preview, screenshot, shader, or output scheduling code.
|
||||
|
||||
|
||||
@@ -561,22 +561,37 @@ components:
|
||||
type: number
|
||||
previewFps:
|
||||
type: number
|
||||
enableExternalKeying:
|
||||
type: boolean
|
||||
videoInputBackend:
|
||||
type: string
|
||||
enum: [decklink, none]
|
||||
videoOutputBackend:
|
||||
type: string
|
||||
enum: [decklink, none]
|
||||
inputVideoFormat:
|
||||
type: string
|
||||
inputFrameRate:
|
||||
type: string
|
||||
outputVideoFormat:
|
||||
type: string
|
||||
outputFrameRate:
|
||||
type: string
|
||||
input:
|
||||
type: object
|
||||
properties:
|
||||
backend:
|
||||
type: string
|
||||
enum: [decklink, none]
|
||||
device:
|
||||
type: string
|
||||
resolution:
|
||||
type: string
|
||||
frameRate:
|
||||
type: string
|
||||
output:
|
||||
type: object
|
||||
properties:
|
||||
backend:
|
||||
type: string
|
||||
enum: [decklink, none]
|
||||
device:
|
||||
type: string
|
||||
resolution:
|
||||
type: string
|
||||
frameRate:
|
||||
type: string
|
||||
keying:
|
||||
type: object
|
||||
properties:
|
||||
external:
|
||||
type: boolean
|
||||
alphaRequired:
|
||||
type: boolean
|
||||
RuntimeStatus:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -200,19 +200,22 @@ Currently consumed fields:
|
||||
- `oscBindAddress` (reported for compatibility; OSC ingress is not wired yet)
|
||||
- `oscPort` (reported for compatibility; OSC ingress is not wired yet)
|
||||
- `oscSmoothing` (reported for compatibility; OSC ingress is not wired yet)
|
||||
- `inputVideoFormat`
|
||||
- `inputFrameRate`
|
||||
- `outputVideoFormat`
|
||||
- `outputFrameRate`
|
||||
- `videoInputBackend`
|
||||
- `videoOutputBackend`
|
||||
- `input.backend`
|
||||
- `input.device`
|
||||
- `input.resolution`
|
||||
- `input.frameRate`
|
||||
- `output.backend`
|
||||
- `output.device`
|
||||
- `output.resolution`
|
||||
- `output.frameRate`
|
||||
- `output.keying.external`
|
||||
- `output.keying.alphaRequired`
|
||||
- `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.
|
||||
`input.backend` and `output.backend` 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.
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ int main(int argc, char** argv)
|
||||
|
||||
SystemFrameExchangeConfig frameExchangeConfig;
|
||||
VideoFormatDimensions(
|
||||
appConfig.outputVideoFormat,
|
||||
appConfig.output.resolution,
|
||||
frameExchangeConfig.width,
|
||||
frameExchangeConfig.height);
|
||||
frameExchangeConfig.pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
@@ -95,7 +95,7 @@ int main(int argc, char** argv)
|
||||
|
||||
InputFrameMailboxConfig inputMailboxConfig;
|
||||
VideoFormatDimensions(
|
||||
appConfig.inputVideoFormat,
|
||||
appConfig.input.resolution,
|
||||
inputMailboxConfig.width,
|
||||
inputMailboxConfig.height);
|
||||
inputMailboxConfig.pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
@@ -107,24 +107,24 @@ int main(int argc, char** argv)
|
||||
VideoFormat inputVideoMode;
|
||||
VideoFormat outputVideoMode;
|
||||
std::string inputVideoModeError;
|
||||
const bool inputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.inputVideoFormat, appConfig.inputFrameRate, inputVideoMode);
|
||||
const bool outputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.outputVideoFormat, appConfig.outputFrameRate, outputVideoMode);
|
||||
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 inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
|
||||
appConfig.inputVideoFormat + " / " + appConfig.inputFrameRate;
|
||||
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 outputVideoFormat/outputFrameRate in config/runtime-host.json; render cadence will use parsed frame-rate fallback: " +
|
||||
appConfig.outputVideoFormat + " / " + appConfig.outputFrameRate);
|
||||
"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.deckLink.outputVideoMode = outputVideoMode;
|
||||
appConfig.output.videoMode = outputVideoMode;
|
||||
}
|
||||
|
||||
auto inputBackend = RenderCadenceCompositor::StartVideoInputBackend(
|
||||
@@ -137,7 +137,7 @@ int main(int argc, char** argv)
|
||||
RenderThread::Config renderConfig;
|
||||
renderConfig.width = frameExchangeConfig.width;
|
||||
renderConfig.height = frameExchangeConfig.height;
|
||||
const double fallbackFrameDurationMilliseconds = FrameDurationMillisecondsFromRateString(appConfig.outputFrameRate);
|
||||
const double fallbackFrameDurationMilliseconds = FrameDurationMillisecondsFromRateString(appConfig.output.frameRate);
|
||||
renderConfig.frameDurationMilliseconds = outputVideoModeResolved
|
||||
? outputVideoMode.frameDurationMilliseconds
|
||||
: fallbackFrameDurationMilliseconds;
|
||||
|
||||
@@ -5,8 +5,16 @@ namespace RenderCadenceCompositor
|
||||
AppConfig DefaultAppConfig()
|
||||
{
|
||||
AppConfig config;
|
||||
config.deckLink.externalKeyingEnabled = false;
|
||||
config.deckLink.outputAlphaRequired = false;
|
||||
config.input.backend = "decklink";
|
||||
config.input.device = "default";
|
||||
config.input.resolution = "1080p";
|
||||
config.input.frameRate = "59.94";
|
||||
config.output.backend = "decklink";
|
||||
config.output.device = "default";
|
||||
config.output.resolution = "1080p";
|
||||
config.output.frameRate = "59.94";
|
||||
config.output.externalKeyingEnabled = false;
|
||||
config.output.outputAlphaRequired = false;
|
||||
config.outputThread.targetBufferedFrames = 4;
|
||||
config.telemetry.interval = std::chrono::seconds(1);
|
||||
config.logging.minimumLevel = LogLevel::Log;
|
||||
@@ -22,12 +30,6 @@ AppConfig DefaultAppConfig()
|
||||
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.videoInputBackend = "decklink";
|
||||
config.videoOutputBackend = "decklink";
|
||||
config.autoReload = true;
|
||||
config.maxTemporalHistoryFrames = 12;
|
||||
config.previewEnabled = false;
|
||||
|
||||
@@ -13,9 +13,29 @@
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
struct VideoInputAppConfig
|
||||
{
|
||||
std::string backend = "decklink";
|
||||
std::string device = "default";
|
||||
std::string resolution = "1080p";
|
||||
std::string frameRate = "59.94";
|
||||
};
|
||||
|
||||
struct VideoOutputAppConfig
|
||||
{
|
||||
std::string backend = "decklink";
|
||||
std::string device = "default";
|
||||
std::string resolution = "1080p";
|
||||
std::string frameRate = "59.94";
|
||||
bool externalKeyingEnabled = false;
|
||||
bool outputAlphaRequired = false;
|
||||
VideoFormat videoMode;
|
||||
};
|
||||
|
||||
struct AppConfig
|
||||
{
|
||||
VideoOutputEdgeConfig deckLink;
|
||||
VideoInputAppConfig input;
|
||||
VideoOutputAppConfig output;
|
||||
VideoOutputThreadConfig outputThread;
|
||||
TelemetryHealthMonitorConfig telemetry;
|
||||
LoggerConfig logging;
|
||||
@@ -24,12 +44,6 @@ struct AppConfig
|
||||
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";
|
||||
std::string videoInputBackend = "decklink";
|
||||
std::string videoOutputBackend = "decklink";
|
||||
bool autoReload = true;
|
||||
std::size_t maxTemporalHistoryFrames = 12;
|
||||
bool previewEnabled = false;
|
||||
|
||||
@@ -79,6 +79,37 @@ void ApplyPort(const JsonValue& root, const char* key, unsigned short& target)
|
||||
if (port >= 1.0 && port <= 65535.0)
|
||||
target = static_cast<unsigned short>(port);
|
||||
}
|
||||
|
||||
void ApplyInputConfig(const JsonValue& root, AppConfig& config)
|
||||
{
|
||||
const JsonValue* input = Find(root, "input");
|
||||
if (!input || !input->isObject())
|
||||
return;
|
||||
|
||||
ApplyString(*input, "backend", config.input.backend);
|
||||
ApplyString(*input, "device", config.input.device);
|
||||
ApplyString(*input, "resolution", config.input.resolution);
|
||||
ApplyString(*input, "frameRate", config.input.frameRate);
|
||||
}
|
||||
|
||||
void ApplyOutputConfig(const JsonValue& root, AppConfig& config)
|
||||
{
|
||||
const JsonValue* output = Find(root, "output");
|
||||
if (!output || !output->isObject())
|
||||
return;
|
||||
|
||||
ApplyString(*output, "backend", config.output.backend);
|
||||
ApplyString(*output, "device", config.output.device);
|
||||
ApplyString(*output, "resolution", config.output.resolution);
|
||||
ApplyString(*output, "frameRate", config.output.frameRate);
|
||||
|
||||
const JsonValue* keying = Find(*output, "keying");
|
||||
if (keying && keying->isObject())
|
||||
{
|
||||
ApplyBool(*keying, "external", config.output.externalKeyingEnabled);
|
||||
ApplyBool(*keying, "alphaRequired", config.output.outputAlphaRequired);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppConfigProvider::AppConfigProvider() :
|
||||
@@ -124,17 +155,12 @@ bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& err
|
||||
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);
|
||||
ApplyString(root, "videoInputBackend", mConfig.videoInputBackend);
|
||||
ApplyString(root, "videoOutputBackend", mConfig.videoOutputBackend);
|
||||
ApplyInputConfig(root, mConfig);
|
||||
ApplyOutputConfig(root, mConfig);
|
||||
ApplyBool(root, "autoReload", mConfig.autoReload);
|
||||
ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames);
|
||||
ApplyBool(root, "previewEnabled", mConfig.previewEnabled);
|
||||
ApplyDouble(root, "previewFps", mConfig.previewFps);
|
||||
ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
|
||||
|
||||
mLoadedFromFile = true;
|
||||
error.clear();
|
||||
|
||||
@@ -137,7 +137,7 @@ public:
|
||||
private:
|
||||
void StartOptionalVideoOutput()
|
||||
{
|
||||
if (mConfig.videoOutputBackend == "none")
|
||||
if (mConfig.output.backend == "none")
|
||||
{
|
||||
mVideoOutputEnabled = false;
|
||||
mVideoOutputStatus = "Video output backend disabled by config.";
|
||||
@@ -146,9 +146,13 @@ private:
|
||||
}
|
||||
|
||||
std::string outputError;
|
||||
Log("app", "Initializing optional video output backend: " + mConfig.videoOutputBackend + ".");
|
||||
Log("app", "Initializing optional video output backend: " + mConfig.output.backend + ".");
|
||||
VideoOutputEdgeConfig outputConfig;
|
||||
outputConfig.outputVideoMode = mConfig.output.videoMode;
|
||||
outputConfig.externalKeyingEnabled = mConfig.output.externalKeyingEnabled;
|
||||
outputConfig.outputAlphaRequired = mConfig.output.outputAlphaRequired;
|
||||
if (!mOutput->Initialize(
|
||||
mConfig.deckLink,
|
||||
outputConfig,
|
||||
[this](const VideoIOCompletion& completion) {
|
||||
mFrameExchange.ReleaseScheduledByBytes(completion.outputFrameBuffer);
|
||||
},
|
||||
@@ -180,7 +184,7 @@ private:
|
||||
}
|
||||
|
||||
mVideoOutputEnabled = true;
|
||||
mVideoOutputStatus = mConfig.videoOutputBackend + " scheduled output running.";
|
||||
mVideoOutputStatus = mConfig.output.backend + " scheduled output running.";
|
||||
Log("app", mVideoOutputStatus);
|
||||
Log(
|
||||
"app",
|
||||
|
||||
@@ -274,8 +274,8 @@ const VideoInputBackendRegistration* FindVideoInputBackend(std::string_view back
|
||||
|
||||
bool VideoBackendsRequireCom(const AppConfig& config)
|
||||
{
|
||||
const std::string inputBackend = NormalizeBackendName(config.videoInputBackend);
|
||||
const std::string outputBackend = NormalizeBackendName(config.videoOutputBackend);
|
||||
const std::string inputBackend = NormalizeBackendName(config.input.backend);
|
||||
const std::string outputBackend = NormalizeBackendName(config.output.backend);
|
||||
const VideoInputBackendRegistration* input = FindVideoInputBackend(inputBackend);
|
||||
const VideoOutputBackendRegistration* output = FindVideoOutputBackend(outputBackend);
|
||||
return (input != nullptr && input->requiresCom) || (output != nullptr && output->requiresCom);
|
||||
@@ -283,12 +283,12 @@ bool VideoBackendsRequireCom(const AppConfig& config)
|
||||
|
||||
std::unique_ptr<IVideoOutputEdge> CreateVideoOutputBackend(const AppConfig& config)
|
||||
{
|
||||
const std::string backendName = NormalizeBackendName(config.videoOutputBackend);
|
||||
const std::string backendName = NormalizeBackendName(config.output.backend);
|
||||
const VideoOutputBackendRegistration* backend = FindVideoOutputBackend(backendName);
|
||||
if (backend != nullptr && backend->create != nullptr)
|
||||
return backend->create();
|
||||
|
||||
return std::make_unique<DisabledVideoOutputEdge>("Unsupported videoOutputBackend: " + config.videoOutputBackend);
|
||||
return std::make_unique<DisabledVideoOutputEdge>("Unsupported output backend: " + config.output.backend);
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoInputBackendSession> StartVideoInputBackend(
|
||||
@@ -298,7 +298,7 @@ std::unique_ptr<VideoInputBackendSession> StartVideoInputBackend(
|
||||
const VideoFormat& inputVideoMode,
|
||||
bool inputVideoModeResolved)
|
||||
{
|
||||
const std::string backendName = NormalizeBackendName(config.videoInputBackend);
|
||||
const std::string backendName = NormalizeBackendName(config.input.backend);
|
||||
const VideoInputBackendRegistration* backend = FindVideoInputBackend(backendName);
|
||||
if (backend != nullptr && backend->start != nullptr)
|
||||
{
|
||||
@@ -306,7 +306,7 @@ std::unique_ptr<VideoInputBackendSession> StartVideoInputBackend(
|
||||
return backend->start(context);
|
||||
}
|
||||
|
||||
LogWarning("app", "Unsupported videoInputBackend '" + config.videoInputBackend + "'; runtime shaders will use fallback input.");
|
||||
return std::make_unique<NoInputBackendSession>(config.videoInputBackend);
|
||||
LogWarning("app", "Unsupported input backend '" + config.input.backend + "'; runtime shaders will use fallback input.");
|
||||
return std::make_unique<NoInputBackendSession>(config.input.backend);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,13 @@ struct RuntimeStateJsonInput
|
||||
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||
{
|
||||
writer.BeginObject();
|
||||
writer.KeyString("backend", input.config.videoOutputBackend);
|
||||
writer.KeyString("backend", input.config.output.backend);
|
||||
writer.KeyNull("modelName");
|
||||
writer.KeyBool("supportsInternalKeying", false);
|
||||
writer.KeyBool("supportsExternalKeying", false);
|
||||
writer.KeyBool("keyerInterfaceAvailable", false);
|
||||
writer.KeyBool("externalKeyingRequested", input.config.deckLink.externalKeyingEnabled);
|
||||
writer.KeyBool("externalKeyingActive", input.videoOutputEnabled && input.config.deckLink.externalKeyingEnabled);
|
||||
writer.KeyBool("externalKeyingRequested", input.config.output.externalKeyingEnabled);
|
||||
writer.KeyBool("externalKeyingActive", input.videoOutputEnabled && input.config.output.externalKeyingEnabled);
|
||||
writer.KeyString("statusMessage", input.videoOutputStatus);
|
||||
writer.EndObject();
|
||||
}
|
||||
@@ -41,7 +41,7 @@ inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInp
|
||||
inline void WriteVideoOutputBackendMetricsJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||
{
|
||||
writer.BeginObject();
|
||||
if (input.config.videoOutputBackend == "decklink")
|
||||
if (input.config.output.backend == "decklink")
|
||||
{
|
||||
writer.KeyBool("bufferedAvailable", input.telemetry.deckLinkBufferedAvailable);
|
||||
writer.Key("buffered");
|
||||
@@ -68,7 +68,7 @@ inline void WriteVideoOutputTelemetryJson(JsonWriter& writer, const RuntimeState
|
||||
{
|
||||
writer.BeginObject();
|
||||
writer.KeyBool("enabled", input.videoOutputEnabled);
|
||||
writer.KeyString("backend", input.config.videoOutputBackend);
|
||||
writer.KeyString("backend", input.config.output.backend);
|
||||
writer.KeyString("statusMessage", input.videoOutputStatus);
|
||||
writer.KeyUInt("scheduleFailures", input.telemetry.scheduleFailures);
|
||||
writer.KeyUInt("completions", input.telemetry.completions);
|
||||
@@ -81,7 +81,7 @@ inline void WriteVideoOutputTelemetryJson(JsonWriter& writer, const RuntimeState
|
||||
|
||||
inline void OutputDimensions(const RuntimeStateJsonInput& input, unsigned& width, unsigned& height)
|
||||
{
|
||||
VideoFormatDimensions(input.config.outputVideoFormat, width, height);
|
||||
VideoFormatDimensions(input.config.output.resolution, width, height);
|
||||
}
|
||||
|
||||
inline const char* ShaderParameterTypeName(ShaderParameterType type)
|
||||
@@ -291,13 +291,25 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||
writer.KeyBool("autoReload", input.config.autoReload);
|
||||
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);
|
||||
writer.KeyString("outputFrameRate", input.config.outputFrameRate);
|
||||
writer.Key("input");
|
||||
writer.BeginObject();
|
||||
writer.KeyString("backend", input.config.input.backend);
|
||||
writer.KeyString("device", input.config.input.device);
|
||||
writer.KeyString("resolution", input.config.input.resolution);
|
||||
writer.KeyString("frameRate", input.config.input.frameRate);
|
||||
writer.EndObject();
|
||||
writer.Key("output");
|
||||
writer.BeginObject();
|
||||
writer.KeyString("backend", input.config.output.backend);
|
||||
writer.KeyString("device", input.config.output.device);
|
||||
writer.KeyString("resolution", input.config.output.resolution);
|
||||
writer.KeyString("frameRate", input.config.output.frameRate);
|
||||
writer.Key("keying");
|
||||
writer.BeginObject();
|
||||
writer.KeyBool("external", input.config.output.externalKeyingEnabled);
|
||||
writer.KeyBool("alphaRequired", input.config.output.outputAlphaRequired);
|
||||
writer.EndObject();
|
||||
writer.EndObject();
|
||||
writer.EndObject();
|
||||
|
||||
writer.Key("runtime");
|
||||
@@ -315,7 +327,7 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||
writer.KeyBool("hasSignal", input.videoOutputEnabled);
|
||||
writer.KeyUInt("width", outputWidth);
|
||||
writer.KeyUInt("height", outputHeight);
|
||||
writer.KeyString("modeName", input.config.outputVideoFormat + " output-only");
|
||||
writer.KeyString("modeName", input.config.output.resolution + " output-only");
|
||||
writer.EndObject();
|
||||
|
||||
writer.Key("videoOutput");
|
||||
@@ -327,7 +339,7 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||
|
||||
writer.Key("performance");
|
||||
writer.BeginObject();
|
||||
writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(input.config.outputFrameRate));
|
||||
writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(input.config.output.frameRate));
|
||||
writer.KeyDouble("renderMs", input.telemetry.renderFrameMilliseconds);
|
||||
writer.KeyNull("smoothedRenderMs");
|
||||
writer.KeyDouble("budgetUsedPercent", input.telemetry.renderFrameBudgetUsedPercent);
|
||||
|
||||
@@ -135,24 +135,24 @@ bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::str
|
||||
}
|
||||
|
||||
bool ResolveConfiguredVideoFormats(
|
||||
const std::string& inputVideoFormat,
|
||||
const std::string& inputResolution,
|
||||
const std::string& inputFrameRate,
|
||||
const std::string& outputVideoFormat,
|
||||
const std::string& outputResolution,
|
||||
const std::string& outputFrameRate,
|
||||
VideoFormatSelection& videoModes,
|
||||
std::string& error)
|
||||
{
|
||||
if (!ResolveConfiguredVideoFormat(inputVideoFormat, inputFrameRate, videoModes.input))
|
||||
if (!ResolveConfiguredVideoFormat(inputResolution, inputFrameRate, videoModes.input))
|
||||
{
|
||||
error = "Unsupported inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
|
||||
inputVideoFormat + " / " + inputFrameRate;
|
||||
error = "Unsupported input resolution/frameRate in config/runtime-host.json: " +
|
||||
inputResolution + " / " + inputFrameRate;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResolveConfiguredVideoFormat(outputVideoFormat, outputFrameRate, videoModes.output))
|
||||
if (!ResolveConfiguredVideoFormat(outputResolution, outputFrameRate, videoModes.output))
|
||||
{
|
||||
error = "Unsupported outputVideoFormat/outputFrameRate in config/runtime-host.json: " +
|
||||
outputVideoFormat + " / " + outputFrameRate;
|
||||
error = "Unsupported output resolution/frameRate in config/runtime-host.json: " +
|
||||
outputResolution + " / " + outputFrameRate;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ double FrameDurationMillisecondsFromRateString(const std::string& rateText, doub
|
||||
void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsigned& height);
|
||||
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode);
|
||||
bool ResolveConfiguredVideoFormats(
|
||||
const std::string& inputVideoFormat,
|
||||
const std::string& inputResolution,
|
||||
const std::string& inputFrameRate,
|
||||
const std::string& outputVideoFormat,
|
||||
const std::string& outputResolution,
|
||||
const std::string& outputFrameRate,
|
||||
VideoFormatSelection& videoModes,
|
||||
std::string& error);
|
||||
|
||||
@@ -360,7 +360,7 @@ bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoF
|
||||
}
|
||||
else if (mState.supportsExternalKeying)
|
||||
{
|
||||
mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
||||
mState.statusMessage = "Selected DeckLink output supports external keying. Set output.keying.external to true in runtime-host.json to request it.";
|
||||
}
|
||||
|
||||
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
|
||||
|
||||
@@ -31,17 +31,26 @@ std::filesystem::path WriteConfigFixture()
|
||||
<< " \"oscBindAddress\": \"127.0.0.1\",\n"
|
||||
<< " \"oscPort\": 9100,\n"
|
||||
<< " \"oscSmoothing\": 0.25,\n"
|
||||
<< " \"inputVideoFormat\": \"720p\",\n"
|
||||
<< " \"inputFrameRate\": \"50\",\n"
|
||||
<< " \"outputVideoFormat\": \"2160p\",\n"
|
||||
<< " \"outputFrameRate\": \"60\",\n"
|
||||
<< " \"videoInputBackend\": \"none\",\n"
|
||||
<< " \"videoOutputBackend\": \"decklink\",\n"
|
||||
<< " \"input\": {\n"
|
||||
<< " \"backend\": \"none\",\n"
|
||||
<< " \"device\": \"input-card-1\",\n"
|
||||
<< " \"resolution\": \"720p\",\n"
|
||||
<< " \"frameRate\": \"50\"\n"
|
||||
<< " },\n"
|
||||
<< " \"output\": {\n"
|
||||
<< " \"backend\": \"decklink\",\n"
|
||||
<< " \"device\": \"output-card-1\",\n"
|
||||
<< " \"resolution\": \"2160p\",\n"
|
||||
<< " \"frameRate\": \"60\",\n"
|
||||
<< " \"keying\": {\n"
|
||||
<< " \"external\": true,\n"
|
||||
<< " \"alphaRequired\": true\n"
|
||||
<< " }\n"
|
||||
<< " },\n"
|
||||
<< " \"autoReload\": false,\n"
|
||||
<< " \"maxTemporalHistoryFrames\": 8,\n"
|
||||
<< " \"previewEnabled\": true,\n"
|
||||
<< " \"previewFps\": 24,\n"
|
||||
<< " \"enableExternalKeying\": true\n"
|
||||
<< " \"previewFps\": 24\n"
|
||||
<< "}\n";
|
||||
return path;
|
||||
}
|
||||
@@ -64,17 +73,20 @@ void TestLoadsRuntimeHostConfig()
|
||||
Expect(config.oscBindAddress == "127.0.0.1", "OSC bind address loads");
|
||||
Expect(config.oscPort == 9100, "OSC port loads");
|
||||
Expect(config.oscSmoothing == 0.25, "OSC smoothing loads");
|
||||
Expect(config.inputVideoFormat == "720p", "input format loads");
|
||||
Expect(config.inputFrameRate == "50", "input frame rate loads");
|
||||
Expect(config.outputVideoFormat == "2160p", "output format loads");
|
||||
Expect(config.outputFrameRate == "60", "output frame rate loads");
|
||||
Expect(config.videoInputBackend == "none", "video input backend loads");
|
||||
Expect(config.videoOutputBackend == "decklink", "video output backend loads");
|
||||
Expect(config.input.resolution == "720p", "input resolution loads");
|
||||
Expect(config.input.frameRate == "50", "input frame rate loads");
|
||||
Expect(config.input.device == "input-card-1", "input device loads");
|
||||
Expect(config.output.resolution == "2160p", "output resolution loads");
|
||||
Expect(config.output.frameRate == "60", "output frame rate loads");
|
||||
Expect(config.output.device == "output-card-1", "output device loads");
|
||||
Expect(config.input.backend == "none", "video input backend loads");
|
||||
Expect(config.output.backend == "decklink", "video output backend loads");
|
||||
Expect(!config.autoReload, "auto reload loads");
|
||||
Expect(config.maxTemporalHistoryFrames == 8, "history length loads");
|
||||
Expect(config.previewEnabled, "preview enabled toggle loads");
|
||||
Expect(config.previewFps == 24.0, "preview fps loads");
|
||||
Expect(config.deckLink.externalKeyingEnabled, "external keying loads");
|
||||
Expect(config.output.externalKeyingEnabled, "external keying loads");
|
||||
Expect(config.output.outputAlphaRequired, "output alpha requirement loads");
|
||||
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ void WriteFile(const std::filesystem::path& path, const std::string& contents)
|
||||
int main()
|
||||
{
|
||||
RenderCadenceCompositor::AppConfig config = RenderCadenceCompositor::DefaultAppConfig();
|
||||
config.outputVideoFormat = "1080p";
|
||||
config.outputFrameRate = "59.94";
|
||||
config.output.resolution = "1080p";
|
||||
config.output.frameRate = "59.94";
|
||||
|
||||
RenderCadenceCompositor::CadenceTelemetrySnapshot telemetry;
|
||||
telemetry.renderFps = 59.94;
|
||||
@@ -117,8 +117,8 @@ int main()
|
||||
ExpectContains(json, "\"type\":\"color\"", "state JSON should serialize parameter types for the UI");
|
||||
ExpectContains(json, "\"width\":1920", "state JSON should expose output width");
|
||||
ExpectContains(json, "\"height\":1080", "state JSON should expose output height");
|
||||
ExpectContains(json, "\"videoInputBackend\":\"decklink\"", "state JSON should expose input backend");
|
||||
ExpectContains(json, "\"videoOutputBackend\":\"decklink\"", "state JSON should expose output backend");
|
||||
ExpectContains(json, "\"input\":{\"backend\":\"decklink\",\"device\":\"default\",\"resolution\":\"1080p\",\"frameRate\":\"59.94\"}", "state JSON should expose nested input config");
|
||||
ExpectContains(json, "\"output\":{\"backend\":\"decklink\",\"device\":\"default\",\"resolution\":\"1080p\",\"frameRate\":\"59.94\",\"keying\"", "state JSON should expose nested output config");
|
||||
ExpectContains(json, "\"videoOutput\":{\"enabled\":true,\"backend\":\"decklink\"", "state JSON should expose neutral video output status");
|
||||
ExpectContains(json, "\"scheduleFailures\":2", "state JSON should expose neutral video output schedule failures");
|
||||
ExpectContains(json, "\"backendMetrics\":{\"bufferedAvailable\":true,\"buffered\":4", "state JSON should expose backend-specific video output metrics");
|
||||
|
||||
Reference in New Issue
Block a user