Clean up video IO
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Failing after 2m33s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-22 15:16:35 +10:00
parent 7bf5464fd2
commit 9787ca5f27
15 changed files with 377 additions and 255 deletions

View File

@@ -106,6 +106,14 @@ set(SHADER_MANIFEST_SOURCES
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp" "${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
) )
set(VIDEO_MODE_SOURCES
"${SRC_DIR}/video/core/VideoMode.cpp"
)
set(VIDEO_FORMAT_SOURCES
"${SRC_DIR}/video/core/VideoIOFormat.cpp"
)
set(SLANG_RUNTIME_FILES set(SLANG_RUNTIME_FILES
"${SLANG_ROOT}/bin/slangc.exe" "${SLANG_ROOT}/bin/slangc.exe"
"${SLANG_ROOT}/bin/slang-compiler.dll" "${SLANG_ROOT}/bin/slang-compiler.dll"

View File

@@ -23,7 +23,7 @@ Native app internals are grouped by boundary:
- `render/`: render thread, readback, runtime render scene, and shared-context shader program preparation. - `render/`: render thread, readback, runtime render scene, and shared-context shader program preparation.
- `runtime/`: shader catalog support, layer model, Slang build bridge, font atlas build, and runtime-state persistence. - `runtime/`: shader catalog support, layer model, Slang build bridge, font atlas build, and runtime-state persistence.
- `shader/`: shader package parsing and Slang compilation helpers. - `shader/`: shader package parsing and Slang compilation helpers.
- `video/core/`: backend-neutral video IO handoff contracts, formats, and output scheduling thread. - `video/core/`: backend-neutral video IO handoff contracts, mode descriptions, pixel formats, and output scheduling thread.
- `video/decklink/`: current DeckLink input/output backend. - `video/decklink/`: current DeckLink input/output backend.
- `video/playout/`: backend-adjacent playout policy, queues, frame pools, and scheduling helpers. - `video/playout/`: backend-adjacent playout policy, queues, frame pools, and scheduling helpers.
- `video/legacy/`: older backend pipeline pieces kept separate while the new edge model settles. - `video/legacy/`: older backend pipeline pieces kept separate while the new edge model settles.
@@ -164,7 +164,7 @@ Current native test coverage includes:
} }
``` ```
`inputVideoFormat`/`inputFrameRate` select the video capture mode. `outputVideoFormat`/`outputFrameRate` select the playout mode. With the current DeckLink backend, supported modes 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`. `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`.
Legacy `videoFormat` and `frameRate` keys are still accepted and apply to both input and output unless the explicit input/output keys are present. Legacy `videoFormat` and `frameRate` keys are still accepted and apply to both input and output unless the explicit input/output keys are present.

View File

@@ -25,7 +25,7 @@ Primary source areas:
- `src/render/thread`: render thread lifecycle, cadence loop, metrics, and runtime shader commit mailbox - `src/render/thread`: render thread lifecycle, cadence loop, metrics, and runtime shader commit mailbox
- `src/render/runtime`: render-thread-owned runtime shader scene, renderer, text texture upload cache, and shared-context shader prepare worker - `src/render/runtime`: render-thread-owned runtime shader scene, renderer, text texture upload cache, and shared-context shader prepare worker
- `src/frames`: system-memory frame exchange - `src/frames`: system-memory frame exchange
- `src/video/core`: generic video IO edge contracts, formats, and output scheduling thread - `src/video/core`: generic video IO edge contracts, mode descriptions, formats, and output scheduling thread
- `src/video/decklink`: current DeckLink input/output backend - `src/video/decklink`: current DeckLink input/output backend
- `src/video/playout`: backend-adjacent playout policy, queues, frame pools, and scheduling helpers - `src/video/playout`: backend-adjacent playout policy, queues, frame pools, and scheduling helpers
- `src/video/legacy`: older backend pipeline pieces kept separate from the current app path - `src/video/legacy`: older backend pipeline pieces kept separate from the current app path
@@ -133,7 +133,7 @@ When a runtime shader build completes, the app publishes a render-layer artifact
## Video And Preview ## Video And Preview
Video input and output are optional edges. DeckLink is the current concrete backend. Video input and output are optional edges. DeckLink is the current concrete backend. 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. 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.

View File

@@ -7,8 +7,9 @@
#include "render/thread/RenderThread.h" #include "render/thread/RenderThread.h"
#include "video/decklink/DeckLinkInput.h" #include "video/decklink/DeckLinkInput.h"
#include "video/decklink/DeckLinkInputThread.h" #include "video/decklink/DeckLinkInputThread.h"
#include "DeckLinkDisplayMode.h" #include "video/decklink/DeckLinkDisplayMode.h"
#include "VideoIOFormat.h" #include "video/core/VideoIOFormat.h"
#include "video/core/VideoMode.h"
#include <windows.h> #include <windows.h>
@@ -93,7 +94,7 @@ int main(int argc, char** argv)
} }
SystemFrameExchangeConfig frameExchangeConfig; SystemFrameExchangeConfig frameExchangeConfig;
RenderCadenceCompositor::VideoFormatDimensions( VideoFormatDimensions(
appConfig.outputVideoFormat, appConfig.outputVideoFormat,
frameExchangeConfig.width, frameExchangeConfig.width,
frameExchangeConfig.height); frameExchangeConfig.height);
@@ -108,7 +109,7 @@ int main(int argc, char** argv)
SystemFrameExchange frameExchange(frameExchangeConfig); SystemFrameExchange frameExchange(frameExchangeConfig);
InputFrameMailboxConfig inputMailboxConfig; InputFrameMailboxConfig inputMailboxConfig;
RenderCadenceCompositor::VideoFormatDimensions( VideoFormatDimensions(
appConfig.inputVideoFormat, appConfig.inputVideoFormat,
inputMailboxConfig.width, inputMailboxConfig.width,
inputMailboxConfig.height); inputMailboxConfig.height);
@@ -194,9 +195,9 @@ int main(int argc, char** argv)
RenderThread::Config renderConfig; RenderThread::Config renderConfig;
renderConfig.width = frameExchangeConfig.width; renderConfig.width = frameExchangeConfig.width;
renderConfig.height = frameExchangeConfig.height; renderConfig.height = frameExchangeConfig.height;
const double fallbackFrameDurationMilliseconds = RenderCadenceCompositor::FrameDurationMillisecondsFromRateString(appConfig.outputFrameRate); const double fallbackFrameDurationMilliseconds = FrameDurationMillisecondsFromRateString(appConfig.outputFrameRate);
renderConfig.frameDurationMilliseconds = outputVideoModeResolved renderConfig.frameDurationMilliseconds = outputVideoModeResolved
? RenderCadenceCompositor::FrameDurationMillisecondsFromDisplayMode(outputVideoMode.displayMode, fallbackFrameDurationMilliseconds) ? outputVideoMode.frameDurationMilliseconds
: fallbackFrameDurationMilliseconds; : fallbackFrameDurationMilliseconds;
renderConfig.pboDepth = kReadbackDepth; renderConfig.pboDepth = kReadbackDepth;

View File

@@ -2,8 +2,6 @@
#include "RuntimeJson.h" #include "RuntimeJson.h"
#include <algorithm>
#include <cctype>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <fstream> #include <fstream>
@@ -166,92 +164,6 @@ void AppConfigProvider::ApplyCommandLine(int argc, char** argv)
} }
} }
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;
}
double FrameDurationMillisecondsFromDisplayMode(BMDDisplayMode displayMode, double fallbackMilliseconds)
{
struct ModeRate
{
BMDDisplayMode mode;
int64_t frameDuration;
int64_t timeScale;
};
static const ModeRate rates[] =
{
{ bmdModeHD720p50, 1, 50 },
{ bmdModeHD720p5994, 1001, 60000 },
{ bmdModeHD720p60, 1, 60 },
{ bmdModeHD1080i50, 1, 25 },
{ bmdModeHD1080i5994, 1001, 30000 },
{ bmdModeHD1080i6000, 1, 30 },
{ bmdModeHD1080p2398, 1001, 24000 },
{ bmdModeHD1080p24, 1, 24 },
{ bmdModeHD1080p25, 1, 25 },
{ bmdModeHD1080p2997, 1001, 30000 },
{ bmdModeHD1080p30, 1, 30 },
{ bmdModeHD1080p50, 1, 50 },
{ bmdModeHD1080p5994, 1001, 60000 },
{ bmdModeHD1080p6000, 1, 60 },
{ bmdMode4K2160p2398, 1001, 24000 },
{ bmdMode4K2160p24, 1, 24 },
{ bmdMode4K2160p25, 1, 25 },
{ bmdMode4K2160p2997, 1001, 30000 },
{ bmdMode4K2160p30, 1, 30 },
{ bmdMode4K2160p50, 1, 50 },
{ bmdMode4K2160p5994, 1001, 60000 },
{ bmdMode4K2160p60, 1, 60 }
};
for (const ModeRate& rate : rates)
{
if (rate.mode == displayMode && rate.timeScale > 0)
return (static_cast<double>(rate.frameDuration) * 1000.0) / static_cast<double>(rate.timeScale);
}
return fallbackMilliseconds;
}
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;
}
std::filesystem::path FindConfigFile(const std::filesystem::path& relativePath) std::filesystem::path FindConfigFile(const std::filesystem::path& relativePath)
{ {
return FindRepoPath(relativePath); return FindRepoPath(relativePath);

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "AppConfig.h" #include "AppConfig.h"
#include "DeckLinkDisplayMode.h" #include "VideoMode.h"
#include <filesystem> #include <filesystem>
#include <string> #include <string>
@@ -27,9 +27,6 @@ private:
bool mLoadedFromFile = false; bool mLoadedFromFile = false;
}; };
double FrameDurationMillisecondsFromRateString(const std::string& rateText, double fallbackRate = 59.94);
double FrameDurationMillisecondsFromDisplayMode(BMDDisplayMode displayMode, double fallbackMilliseconds);
void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsigned& height);
std::filesystem::path FindConfigFile(const std::filesystem::path& relativePath = "config/runtime-host.json"); std::filesystem::path FindConfigFile(const std::filesystem::path& relativePath = "config/runtime-host.json");
std::filesystem::path FindRepoPath(const std::filesystem::path& relativePath); std::filesystem::path FindRepoPath(const std::filesystem::path& relativePath);
} }

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "DeckLinkDisplayMode.h"
#include "VideoIOFormat.h" #include "VideoIOFormat.h"
#include "VideoMode.h"
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>

View File

@@ -0,0 +1,160 @@
#include "VideoMode.h"
#include <algorithm>
#include <cctype>
std::string NormalizeModeToken(const std::string& value)
{
std::string normalized;
for (unsigned char ch : value)
{
if (std::isalnum(ch))
normalized.push_back(static_cast<char>(std::tolower(ch)));
}
return normalized;
}
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;
}
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode)
{
const std::string formatToken = NormalizeModeToken(videoFormat);
const std::string frameToken = NormalizeModeToken(frameRate);
const std::string combinedToken = formatToken + frameToken;
struct ModeOption
{
const char* token;
const char* formatName;
const char* frameRate;
const char* displayName;
};
static const ModeOption options[] =
{
{ "720p50", "720p", "50", "720p50" },
{ "hd720p50", "720p", "50", "720p50" },
{ "720p5994", "720p", "59.94", "720p59.94" },
{ "hd720p5994", "720p", "59.94", "720p59.94" },
{ "720p60", "720p", "60", "720p60" },
{ "hd720p60", "720p", "60", "720p60" },
{ "1080i50", "1080i", "50", "1080i50" },
{ "hd1080i50", "1080i", "50", "1080i50" },
{ "1080i5994", "1080i", "59.94", "1080i59.94" },
{ "hd1080i5994", "1080i", "59.94", "1080i59.94" },
{ "1080i60", "1080i", "60", "1080i60" },
{ "hd1080i60", "1080i", "60", "1080i60" },
{ "1080p2398", "1080p", "23.98", "1080p23.98" },
{ "hd1080p2398", "1080p", "23.98", "1080p23.98" },
{ "1080p24", "1080p", "24", "1080p24" },
{ "hd1080p24", "1080p", "24", "1080p24" },
{ "1080p25", "1080p", "25", "1080p25" },
{ "hd1080p25", "1080p", "25", "1080p25" },
{ "1080p2997", "1080p", "29.97", "1080p29.97" },
{ "hd1080p2997", "1080p", "29.97", "1080p29.97" },
{ "1080p30", "1080p", "30", "1080p30" },
{ "hd1080p30", "1080p", "30", "1080p30" },
{ "1080p50", "1080p", "50", "1080p50" },
{ "hd1080p50", "1080p", "50", "1080p50" },
{ "1080p5994", "1080p", "59.94", "1080p59.94" },
{ "hd1080p5994", "1080p", "59.94", "1080p59.94" },
{ "1080p60", "1080p", "60", "1080p60" },
{ "hd1080p60", "1080p", "60", "1080p60" },
{ "2160p2398", "2160p", "23.98", "2160p23.98" },
{ "4k2160p2398", "2160p", "23.98", "2160p23.98" },
{ "2160p24", "2160p", "24", "2160p24" },
{ "4k2160p24", "2160p", "24", "2160p24" },
{ "2160p25", "2160p", "25", "2160p25" },
{ "4k2160p25", "2160p", "25", "2160p25" },
{ "2160p2997", "2160p", "29.97", "2160p29.97" },
{ "4k2160p2997", "2160p", "29.97", "2160p29.97" },
{ "2160p30", "2160p", "30", "2160p30" },
{ "4k2160p30", "2160p", "30", "2160p30" },
{ "2160p50", "2160p", "50", "2160p50" },
{ "4k2160p50", "2160p", "50", "2160p50" },
{ "2160p5994", "2160p", "59.94", "2160p59.94" },
{ "4k2160p5994", "2160p", "59.94", "2160p59.94" },
{ "2160p60", "2160p", "60", "2160p60" },
{ "4k2160p60", "2160p", "60", "2160p60" }
};
for (const ModeOption& option : options)
{
if (combinedToken == option.token || (frameToken.empty() && formatToken == option.token))
{
videoMode.formatName = option.formatName;
videoMode.frameRate = option.frameRate;
videoMode.displayName = option.displayName;
VideoFormatDimensions(videoMode.formatName, videoMode.frameSize.width, videoMode.frameSize.height);
videoMode.frameDurationMilliseconds = FrameDurationMillisecondsFromRateString(videoMode.frameRate);
return true;
}
}
return false;
}
bool ResolveConfiguredVideoFormats(
const std::string& inputVideoFormat,
const std::string& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error)
{
if (!ResolveConfiguredVideoFormat(inputVideoFormat, inputFrameRate, videoModes.input))
{
error = "Unsupported inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
inputVideoFormat + " / " + inputFrameRate;
return false;
}
if (!ResolveConfiguredVideoFormat(outputVideoFormat, outputFrameRate, videoModes.output))
{
error = "Unsupported outputVideoFormat/outputFrameRate in config/runtime-host.json: " +
outputVideoFormat + " / " + outputFrameRate;
return false;
}
return true;
}

View File

@@ -0,0 +1,48 @@
#pragma once
#include <string>
struct FrameSize
{
unsigned width = 0;
unsigned height = 0;
bool IsEmpty() const { return width == 0 || height == 0; }
};
inline bool operator==(const FrameSize& left, const FrameSize& right)
{
return left.width == right.width && left.height == right.height;
}
inline bool operator!=(const FrameSize& left, const FrameSize& right)
{
return !(left == right);
}
struct VideoFormat
{
std::string formatName = "1080p";
std::string frameRate = "59.94";
std::string displayName = "1080p59.94";
FrameSize frameSize = { 1920, 1080 };
double frameDurationMilliseconds = 1000.0 / 59.94;
};
struct VideoFormatSelection
{
VideoFormat input;
VideoFormat output;
};
std::string NormalizeModeToken(const std::string& value);
double FrameDurationMillisecondsFromRateString(const std::string& rateText, double fallbackRate = 59.94);
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& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error);

View File

@@ -1,16 +1,50 @@
#include "DeckLinkDisplayMode.h" #include "DeckLinkDisplayMode.h"
#include <cctype> bool DeckLinkDisplayModeForVideoFormat(const VideoFormat& videoMode, BMDDisplayMode& displayMode)
{
struct ModeOption
{
const char* displayName;
BMDDisplayMode mode;
};
std::string NormalizeModeToken(const std::string& value) static const ModeOption options[] =
{ {
std::string normalized; { "720p50", bmdModeHD720p50 },
for (unsigned char ch : value) { "720p59.94", bmdModeHD720p5994 },
{ "720p60", bmdModeHD720p60 },
{ "1080i50", bmdModeHD1080i50 },
{ "1080i59.94", bmdModeHD1080i5994 },
{ "1080i60", bmdModeHD1080i6000 },
{ "1080p23.98", bmdModeHD1080p2398 },
{ "1080p24", bmdModeHD1080p24 },
{ "1080p25", bmdModeHD1080p25 },
{ "1080p29.97", bmdModeHD1080p2997 },
{ "1080p30", bmdModeHD1080p30 },
{ "1080p50", bmdModeHD1080p50 },
{ "1080p59.94", bmdModeHD1080p5994 },
{ "1080p60", bmdModeHD1080p6000 },
{ "2160p23.98", bmdMode4K2160p2398 },
{ "2160p24", bmdMode4K2160p24 },
{ "2160p25", bmdMode4K2160p25 },
{ "2160p29.97", bmdMode4K2160p2997 },
{ "2160p30", bmdMode4K2160p30 },
{ "2160p50", bmdMode4K2160p50 },
{ "2160p59.94", bmdMode4K2160p5994 },
{ "2160p60", bmdMode4K2160p60 }
};
const std::string displayToken = NormalizeModeToken(videoMode.displayName);
for (const ModeOption& option : options)
{ {
if (std::isalnum(ch)) if (displayToken == NormalizeModeToken(option.displayName))
normalized.push_back(static_cast<char>(std::tolower(ch))); {
displayMode = option.mode;
return true;
} }
return normalized; }
return false;
} }
bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName) bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName)
@@ -19,108 +53,55 @@ bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::str
if (!ResolveConfiguredVideoFormat(videoFormat, frameRate, videoMode)) if (!ResolveConfiguredVideoFormat(videoFormat, frameRate, videoMode))
return false; return false;
displayMode = videoMode.displayMode; if (!DeckLinkDisplayModeForVideoFormat(videoMode, displayMode))
return false;
displayModeName = videoMode.displayName; displayModeName = videoMode.displayName;
return true; return true;
} }
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode) double FrameDurationMillisecondsFromDisplayMode(BMDDisplayMode displayMode, double fallbackMilliseconds)
{ {
const std::string formatToken = NormalizeModeToken(videoFormat); struct ModeRate
const std::string frameToken = NormalizeModeToken(frameRate);
const std::string combinedToken = formatToken + frameToken;
struct ModeOption
{ {
const char* token;
BMDDisplayMode mode; BMDDisplayMode mode;
const char* displayName; int64_t frameDuration;
int64_t timeScale;
}; };
static const ModeOption options[] = static const ModeRate rates[] =
{ {
{ "720p50", bmdModeHD720p50, "720p50" }, { bmdModeHD720p50, 1, 50 },
{ "hd720p50", bmdModeHD720p50, "720p50" }, { bmdModeHD720p5994, 1001, 60000 },
{ "720p5994", bmdModeHD720p5994, "720p59.94" }, { bmdModeHD720p60, 1, 60 },
{ "hd720p5994", bmdModeHD720p5994, "720p59.94" }, { bmdModeHD1080i50, 1, 25 },
{ "720p60", bmdModeHD720p60, "720p60" }, { bmdModeHD1080i5994, 1001, 30000 },
{ "hd720p60", bmdModeHD720p60, "720p60" }, { bmdModeHD1080i6000, 1, 30 },
{ "1080i50", bmdModeHD1080i50, "1080i50" }, { bmdModeHD1080p2398, 1001, 24000 },
{ "hd1080i50", bmdModeHD1080i50, "1080i50" }, { bmdModeHD1080p24, 1, 24 },
{ "1080i5994", bmdModeHD1080i5994, "1080i59.94" }, { bmdModeHD1080p25, 1, 25 },
{ "hd1080i5994", bmdModeHD1080i5994, "1080i59.94" }, { bmdModeHD1080p2997, 1001, 30000 },
{ "1080i60", bmdModeHD1080i6000, "1080i60" }, { bmdModeHD1080p30, 1, 30 },
{ "hd1080i60", bmdModeHD1080i6000, "1080i60" }, { bmdModeHD1080p50, 1, 50 },
{ "1080p2398", bmdModeHD1080p2398, "1080p23.98" }, { bmdModeHD1080p5994, 1001, 60000 },
{ "hd1080p2398", bmdModeHD1080p2398, "1080p23.98" }, { bmdModeHD1080p6000, 1, 60 },
{ "1080p24", bmdModeHD1080p24, "1080p24" }, { bmdMode4K2160p2398, 1001, 24000 },
{ "hd1080p24", bmdModeHD1080p24, "1080p24" }, { bmdMode4K2160p24, 1, 24 },
{ "1080p25", bmdModeHD1080p25, "1080p25" }, { bmdMode4K2160p25, 1, 25 },
{ "hd1080p25", bmdModeHD1080p25, "1080p25" }, { bmdMode4K2160p2997, 1001, 30000 },
{ "1080p2997", bmdModeHD1080p2997, "1080p29.97" }, { bmdMode4K2160p30, 1, 30 },
{ "hd1080p2997", bmdModeHD1080p2997, "1080p29.97" }, { bmdMode4K2160p50, 1, 50 },
{ "1080p30", bmdModeHD1080p30, "1080p30" }, { bmdMode4K2160p5994, 1001, 60000 },
{ "hd1080p30", bmdModeHD1080p30, "1080p30" }, { bmdMode4K2160p60, 1, 60 }
{ "1080p50", bmdModeHD1080p50, "1080p50" },
{ "hd1080p50", bmdModeHD1080p50, "1080p50" },
{ "1080p5994", bmdModeHD1080p5994, "1080p59.94" },
{ "hd1080p5994", bmdModeHD1080p5994, "1080p59.94" },
{ "1080p60", bmdModeHD1080p6000, "1080p60" },
{ "hd1080p60", bmdModeHD1080p6000, "1080p60" },
{ "2160p2398", bmdMode4K2160p2398, "2160p23.98" },
{ "4k2160p2398", bmdMode4K2160p2398, "2160p23.98" },
{ "2160p24", bmdMode4K2160p24, "2160p24" },
{ "4k2160p24", bmdMode4K2160p24, "2160p24" },
{ "2160p25", bmdMode4K2160p25, "2160p25" },
{ "4k2160p25", bmdMode4K2160p25, "2160p25" },
{ "2160p2997", bmdMode4K2160p2997, "2160p29.97" },
{ "4k2160p2997", bmdMode4K2160p2997, "2160p29.97" },
{ "2160p30", bmdMode4K2160p30, "2160p30" },
{ "4k2160p30", bmdMode4K2160p30, "2160p30" },
{ "2160p50", bmdMode4K2160p50, "2160p50" },
{ "4k2160p50", bmdMode4K2160p50, "2160p50" },
{ "2160p5994", bmdMode4K2160p5994, "2160p59.94" },
{ "4k2160p5994", bmdMode4K2160p5994, "2160p59.94" },
{ "2160p60", bmdMode4K2160p60, "2160p60" },
{ "4k2160p60", bmdMode4K2160p60, "2160p60" }
}; };
for (const ModeOption& option : options) for (const ModeRate& rate : rates)
{ {
if (combinedToken == option.token || (frameToken.empty() && formatToken == option.token)) if (rate.mode == displayMode && rate.timeScale > 0)
{ return (static_cast<double>(rate.frameDuration) * 1000.0) / static_cast<double>(rate.timeScale);
videoMode.displayMode = option.mode;
videoMode.displayName = option.displayName;
return true;
}
} }
return false; return fallbackMilliseconds;
}
bool ResolveConfiguredVideoFormats(
const std::string& inputVideoFormat,
const std::string& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error)
{
if (!ResolveConfiguredVideoFormat(inputVideoFormat, inputFrameRate, videoModes.input))
{
error = "Unsupported DeckLink inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
inputVideoFormat + " / " + inputFrameRate;
return false;
}
if (!ResolveConfiguredVideoFormat(outputVideoFormat, outputFrameRate, videoModes.output))
{
error = "Unsupported DeckLink outputVideoFormat/outputFrameRate in config/runtime-host.json: " +
outputVideoFormat + " / " + outputFrameRate;
return false;
}
return true;
} }
bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode) bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode)

View File

@@ -1,47 +1,11 @@
#pragma once #pragma once
#include "DeckLinkAPI_h.h" #include "DeckLinkAPI_h.h"
#include "VideoMode.h"
#include <string> #include <string>
struct FrameSize bool DeckLinkDisplayModeForVideoFormat(const VideoFormat& videoMode, BMDDisplayMode& displayMode);
{
unsigned width = 0;
unsigned height = 0;
bool IsEmpty() const { return width == 0 || height == 0; }
};
inline bool operator==(const FrameSize& left, const FrameSize& right)
{
return left.width == right.width && left.height == right.height;
}
inline bool operator!=(const FrameSize& left, const FrameSize& right)
{
return !(left == right);
}
struct VideoFormat
{
BMDDisplayMode displayMode = bmdModeHD1080p5994;
std::string displayName = "1080p59.94";
};
struct VideoFormatSelection
{
VideoFormat input;
VideoFormat output;
};
std::string NormalizeModeToken(const std::string& value);
bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName); bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName);
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode); double FrameDurationMillisecondsFromDisplayMode(BMDDisplayMode displayMode, double fallbackMilliseconds);
bool ResolveConfiguredVideoFormats(
const std::string& inputVideoFormat,
const std::string& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error);
bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode); bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode);

View File

@@ -86,10 +86,17 @@ bool DeckLinkInput::Initialize(const DeckLinkInputConfig& config, std::string& e
mConfig = config; mConfig = config;
Log("decklink-input", "Initializing DeckLink input for " + config.videoFormat.displayName + "."); Log("decklink-input", "Initializing DeckLink input for " + config.videoFormat.displayName + ".");
BMDDisplayMode displayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(config.videoFormat, displayMode))
{
error = "DeckLink input setup failed while mapping " + config.videoFormat.displayName + " to a DeckLink display mode.";
return false;
}
if (!DiscoverInput(config, error)) if (!DiscoverInput(config, error))
return false; return false;
if (mInput->EnableVideoInput(config.videoFormat.displayMode, mCapturePixelFormat, bmdVideoInputFlagDefault) != S_OK) if (mInput->EnableVideoInput(displayMode, mCapturePixelFormat, bmdVideoInputFlagDefault) != S_OK)
{ {
error = "DeckLink input setup failed while enabling " + error = "DeckLink input setup failed while enabling " +
std::string(mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8") + std::string(mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8") +
@@ -260,6 +267,13 @@ void DeckLinkInput::HandleFormatChanged()
bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string& error) bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string& error)
{ {
BMDDisplayMode targetDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(config.videoFormat, targetDisplayMode))
{
error = "DeckLink input discovery failed while mapping " + config.videoFormat.displayName + " to a DeckLink display mode.";
return false;
}
CComPtr<IDeckLinkIterator> iterator; CComPtr<IDeckLinkIterator> iterator;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&iterator)); HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&iterator));
if (FAILED(result)) if (FAILED(result))
@@ -275,8 +289,8 @@ bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string
if (deckLink->QueryInterface(IID_IDeckLinkInput, reinterpret_cast<void**>(&candidateInput)) == S_OK && candidateInput != nullptr) if (deckLink->QueryInterface(IID_IDeckLinkInput, reinterpret_cast<void**>(&candidateInput)) == S_OK && candidateInput != nullptr)
{ {
CComPtr<IDeckLinkDisplayMode> displayMode; CComPtr<IDeckLinkDisplayMode> displayMode;
if (FindInputDisplayMode(candidateInput, config.videoFormat.displayMode, &displayMode) && if (FindInputDisplayMode(candidateInput, targetDisplayMode, &displayMode) &&
SupportsInputFormat(candidateInput, config.videoFormat.displayMode, bmdFormat8BitBGRA)) SupportsInputFormat(candidateInput, targetDisplayMode, bmdFormat8BitBGRA))
{ {
mInput = candidateInput; mInput = candidateInput;
mCapturePixelFormat = bmdFormat8BitBGRA; mCapturePixelFormat = bmdFormat8BitBGRA;
@@ -284,7 +298,7 @@ bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string
return true; return true;
} }
if (displayMode != nullptr && if (displayMode != nullptr &&
SupportsInputFormat(candidateInput, config.videoFormat.displayMode, bmdFormat8BitYUV)) SupportsInputFormat(candidateInput, targetDisplayMode, bmdFormat8BitYUV))
{ {
mInput = candidateInput; mInput = candidateInput;
mCapturePixelFormat = bmdFormat8BitYUV; mCapturePixelFormat = bmdFormat8BitYUV;

View File

@@ -169,6 +169,19 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
CComPtr<IDeckLinkIterator> deckLinkIterator; CComPtr<IDeckLinkIterator> deckLinkIterator;
CComPtr<IDeckLinkDisplayMode> inputMode; CComPtr<IDeckLinkDisplayMode> inputMode;
CComPtr<IDeckLinkDisplayMode> outputMode; CComPtr<IDeckLinkDisplayMode> outputMode;
BMDDisplayMode inputDisplayMode = bmdModeUnknown;
BMDDisplayMode outputDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(videoModes.input, inputDisplayMode))
{
error = "Cannot map configured input mode to DeckLink BMDDisplayMode: " + videoModes.input.displayName;
return false;
}
if (!DeckLinkDisplayModeForVideoFormat(videoModes.output, outputDisplayMode))
{
error = "Cannot map configured output mode to DeckLink BMDDisplayMode: " + videoModes.output.displayName;
return false;
}
mState.inputDisplayModeName = videoModes.input.displayName; mState.inputDisplayModeName = videoModes.input.displayName;
mState.outputDisplayModeName = videoModes.output.displayName; mState.outputDisplayModeName = videoModes.output.displayName;
@@ -250,7 +263,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
return false; return false;
} }
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, videoModes.input.displayMode, &inputMode)) if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, inputDisplayMode, &inputMode))
{ {
error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName; error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName;
ReleaseResources(); ReleaseResources();
@@ -266,7 +279,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
return false; return false;
} }
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, videoModes.output.displayMode, &outputMode)) if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, outputDisplayMode, &outputMode))
{ {
error = "Cannot get specified output BMDDisplayMode for configured mode: " + videoModes.output.displayName; error = "Cannot get specified output BMDDisplayMode for configured mode: " + videoModes.output.displayName;
ReleaseResources(); ReleaseResources();
@@ -304,14 +317,22 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo
} }
mState.formatStatusMessage.clear(); mState.formatStatusMessage.clear();
BMDDisplayMode inputDisplayMode = bmdModeUnknown;
BMDDisplayMode outputDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(videoModes.input, inputDisplayMode) ||
!DeckLinkDisplayModeForVideoFormat(videoModes.output, outputDisplayMode))
{
error = "DeckLink format selection failed while mapping configured video modes.";
return false;
}
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV); const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, inputDisplayMode, bmdFormat10BitYUV);
mState.inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8; mState.inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8;
if (input != nullptr && !inputTenBitSupported) if (input != nullptr && !inputTenBitSupported)
mState.formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. "; mState.formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. ";
const bool outputTenBitSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUV); const bool outputTenBitSupported = OutputSupportsFormat(output, outputDisplayMode, bmdFormat10BitYUV);
const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUVA); const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, outputDisplayMode, bmdFormat10BitYUVA);
mState.outputPixelFormat = outputAlphaRequired mState.outputPixelFormat = outputAlphaRequired
? (outputTenBitYuvaSupported ? VideoIOPixelFormat::Yuva10 : VideoIOPixelFormat::Bgra8) ? (outputTenBitYuvaSupported ? VideoIOPixelFormat::Yuva10 : VideoIOPixelFormat::Bgra8)
: (outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8); : (outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8);
@@ -369,8 +390,14 @@ bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFor
return true; return true;
} }
BMDDisplayMode inputDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(inputVideoMode, inputDisplayMode))
{
error = "DeckLink input setup failed while mapping " + inputVideoMode.displayName + " to a DeckLink display mode.";
return false;
}
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat); const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat);
if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK) if (input->EnableVideoInput(inputDisplayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
{ {
if (mState.inputPixelFormat == VideoIOPixelFormat::V210) if (mState.inputPixelFormat == VideoIOPixelFormat::V210)
{ {
@@ -378,7 +405,7 @@ bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFor
mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8; mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8;
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u; mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
mState.captureTextureWidth = mState.inputFrameSize.width / 2u; mState.captureTextureWidth = mState.inputFrameSize.width / 2u;
if (input->EnableVideoInput(inputVideoMode.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK) if (input->EnableVideoInput(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK)
{ {
std::ostringstream status; std::ostringstream status;
status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat) status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat)
@@ -417,7 +444,13 @@ bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoF
{ {
mOutputFrameCallback = std::move(callback); mOutputFrameCallback = std::move(callback);
if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK) BMDDisplayMode outputDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(outputVideoMode, outputDisplayMode))
{
error = "DeckLink output setup failed while mapping " + outputVideoMode.displayName + " to a DeckLink display mode.";
return false;
}
if (output->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK)
{ {
error = "DeckLink output setup failed while enabling video output."; error = "DeckLink output setup failed while enabling video output.";
return false; return false;

View File

@@ -20,13 +20,13 @@ add_video_shader_test(RenderCadenceCompositorTelemetryTests
add_video_shader_test(RenderCadenceCompositorFrameExchangeTests add_video_shader_test(RenderCadenceCompositorFrameExchangeTests
"${SRC_DIR}/frames/SystemFrameExchange.cpp" "${SRC_DIR}/frames/SystemFrameExchange.cpp"
"${SRC_DIR}/video/core/VideoIOFormat.cpp" ${VIDEO_FORMAT_SOURCES}
"${TEST_DIR}/RenderCadenceCompositorFrameExchangeTests.cpp" "${TEST_DIR}/RenderCadenceCompositorFrameExchangeTests.cpp"
) )
add_video_shader_test(RenderCadenceCompositorInputFrameMailboxTests add_video_shader_test(RenderCadenceCompositorInputFrameMailboxTests
"${SRC_DIR}/frames/InputFrameMailbox.cpp" "${SRC_DIR}/frames/InputFrameMailbox.cpp"
"${SRC_DIR}/video/core/VideoIOFormat.cpp" ${VIDEO_FORMAT_SOURCES}
"${TEST_DIR}/RenderCadenceCompositorInputFrameMailboxTests.cpp" "${TEST_DIR}/RenderCadenceCompositorInputFrameMailboxTests.cpp"
) )
@@ -72,6 +72,7 @@ add_video_shader_test(RenderCadenceCompositorSupportedShaderCatalogTests
add_video_shader_test(RenderCadenceCompositorRuntimeStateJsonTests add_video_shader_test(RenderCadenceCompositorRuntimeStateJsonTests
"${SRC_DIR}/app/AppConfig.cpp" "${SRC_DIR}/app/AppConfig.cpp"
"${SRC_DIR}/app/AppConfigProvider.cpp" "${SRC_DIR}/app/AppConfigProvider.cpp"
${VIDEO_MODE_SOURCES}
"${SRC_DIR}/json/JsonWriter.cpp" "${SRC_DIR}/json/JsonWriter.cpp"
${RUNTIME_LAYER_SOURCES} ${RUNTIME_LAYER_SOURCES}
${RUNTIME_TEXT_SOURCES} ${RUNTIME_TEXT_SOURCES}
@@ -96,6 +97,8 @@ target_link_libraries(RenderCadenceCompositorHttpControlServerTests PRIVATE Ws2_
add_video_shader_test(RenderCadenceCompositorAppConfigProviderTests add_video_shader_test(RenderCadenceCompositorAppConfigProviderTests
"${SRC_DIR}/app/AppConfig.cpp" "${SRC_DIR}/app/AppConfig.cpp"
"${SRC_DIR}/app/AppConfigProvider.cpp" "${SRC_DIR}/app/AppConfigProvider.cpp"
${VIDEO_MODE_SOURCES}
"${SRC_DIR}/video/decklink/DeckLinkDisplayMode.cpp"
${RUNTIME_JSON_SOURCES} ${RUNTIME_JSON_SOURCES}
"${TEST_DIR}/RenderCadenceCompositorAppConfigProviderTests.cpp" "${TEST_DIR}/RenderCadenceCompositorAppConfigProviderTests.cpp"
) )
@@ -129,7 +132,7 @@ add_video_shader_test(Std140BufferTests
add_video_shader_test(VideoIOFormatTests add_video_shader_test(VideoIOFormatTests
"${SRC_DIR}/video/decklink/DeckLinkVideoIOFormat.cpp" "${SRC_DIR}/video/decklink/DeckLinkVideoIOFormat.cpp"
"${SRC_DIR}/video/core/VideoIOFormat.cpp" ${VIDEO_FORMAT_SOURCES}
"${TEST_DIR}/VideoIOFormatTests.cpp" "${TEST_DIR}/VideoIOFormatTests.cpp"
) )
@@ -139,7 +142,7 @@ add_video_shader_test(VideoPlayoutSchedulerTests
) )
add_video_shader_test(VideoOutputThreadTests add_video_shader_test(VideoOutputThreadTests
"${SRC_DIR}/video/core/VideoIOFormat.cpp" ${VIDEO_FORMAT_SOURCES}
"${TEST_DIR}/VideoOutputThreadTests.cpp" "${TEST_DIR}/VideoOutputThreadTests.cpp"
) )
@@ -160,7 +163,7 @@ add_video_shader_test(RenderCadenceControllerTests
add_video_shader_test(SystemOutputFramePoolTests add_video_shader_test(SystemOutputFramePoolTests
"${SRC_DIR}/video/playout/SystemOutputFramePool.cpp" "${SRC_DIR}/video/playout/SystemOutputFramePool.cpp"
"${SRC_DIR}/video/core/VideoIOFormat.cpp" ${VIDEO_FORMAT_SOURCES}
"${TEST_DIR}/SystemOutputFramePoolTests.cpp" "${TEST_DIR}/SystemOutputFramePoolTests.cpp"
) )
@@ -170,7 +173,7 @@ add_video_shader_test(VideoBackendLifecycleTests
) )
add_video_shader_test(VideoIODeviceFakeTests add_video_shader_test(VideoIODeviceFakeTests
"${SRC_DIR}/video/core/VideoIOFormat.cpp" ${VIDEO_FORMAT_SOURCES}
"${TEST_DIR}/VideoIODeviceFakeTests.cpp" "${TEST_DIR}/VideoIODeviceFakeTests.cpp"
) )

View File

@@ -1,4 +1,5 @@
#include "AppConfigProvider.h" #include "AppConfigProvider.h"
#include "DeckLinkDisplayMode.h"
#include <chrono> #include <chrono>
#include <filesystem> #include <filesystem>