500 lines
12 KiB
C++
500 lines
12 KiB
C++
#include "AppConfig.hpp"
|
|
|
|
#include <iostream>
|
|
#include <optional>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
bool parseUint32(
|
|
const char* text,
|
|
uint32_t& value)
|
|
{
|
|
uint32_t parsed = 0;
|
|
bool hasDigit = false;
|
|
|
|
for (int i = 0; text[i] != '\0'; ++i)
|
|
{
|
|
if (text[i] < '0' || text[i] > '9')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
hasDigit = true;
|
|
parsed = parsed * 10u +
|
|
static_cast<uint32_t>(text[i] - '0');
|
|
}
|
|
|
|
if (!hasDigit)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
value = parsed;
|
|
return true;
|
|
}
|
|
|
|
bool parseFeedIndex(
|
|
const char* text,
|
|
uint32_t maxFeeds,
|
|
uint32_t& index)
|
|
{
|
|
uint32_t parsed = 0;
|
|
|
|
if (!parseUint32(text, parsed) ||
|
|
parsed >= maxFeeds)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
index = parsed;
|
|
return true;
|
|
}
|
|
|
|
bool parseGrid(
|
|
std::string_view text,
|
|
uint32_t& cols,
|
|
uint32_t& rows)
|
|
{
|
|
const size_t xPos = text.find('x');
|
|
if (xPos == std::string_view::npos)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const std::string colText(text.substr(0, xPos));
|
|
const std::string rowText(text.substr(xPos + 1));
|
|
|
|
uint32_t parsedCols = 0;
|
|
uint32_t parsedRows = 0;
|
|
|
|
if (!parseUint32(colText.c_str(), parsedCols) ||
|
|
!parseUint32(rowText.c_str(), parsedRows) ||
|
|
parsedCols == 0 ||
|
|
parsedRows == 0 ||
|
|
parsedCols > MaxFeedCount / parsedRows)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
cols = parsedCols;
|
|
rows = parsedRows;
|
|
return true;
|
|
}
|
|
|
|
uint32_t feedCount(const AppConfig& config)
|
|
{
|
|
return config.gridCols * config.gridRows;
|
|
}
|
|
|
|
void applyDefaultFeeds(AppConfig& config)
|
|
{
|
|
config.feeds.assign(feedCount(config), FeedConfig{});
|
|
}
|
|
}
|
|
|
|
std::optional<FeedKind> parseFeedKind(std::string_view text)
|
|
{
|
|
if (text == "nosignal" ||
|
|
text == "none" ||
|
|
text == "fake")
|
|
{
|
|
return FeedKind::NoSignal;
|
|
}
|
|
|
|
if (text == "mxl")
|
|
{
|
|
return FeedKind::MxlPlaceholder;
|
|
}
|
|
|
|
if (text == "smpte")
|
|
{
|
|
return FeedKind::SmpteBars;
|
|
}
|
|
|
|
if (text == "mxlsdk")
|
|
{
|
|
return FeedKind::MxlSdk;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<PresentModeConfig> parsePresentMode(std::string_view text)
|
|
{
|
|
if (text == "fifo")
|
|
{
|
|
return PresentModeConfig::Fifo;
|
|
}
|
|
|
|
if (text == "mailbox")
|
|
{
|
|
return PresentModeConfig::Mailbox;
|
|
}
|
|
|
|
if (text == "immediate")
|
|
{
|
|
return PresentModeConfig::Immediate;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::string_view feedKindName(FeedKind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case FeedKind::MxlPlaceholder:
|
|
return "mxl";
|
|
|
|
case FeedKind::SmpteBars:
|
|
return "smpte";
|
|
|
|
case FeedKind::MxlSdk:
|
|
return "mxlsdk";
|
|
|
|
case FeedKind::NoSignal:
|
|
default:
|
|
return "nosignal";
|
|
}
|
|
}
|
|
|
|
void printUsage(const char* executableName)
|
|
{
|
|
std::cout
|
|
<< "Usage: "
|
|
<< executableName
|
|
<< " [options] [feed...]\n"
|
|
<< "Options:\n"
|
|
<< " --no-fps Disable FPS logging\n"
|
|
<< " --perf Log per-frame CPU timing breakdown\n"
|
|
<< " --verbose Enable detailed startup and SDK logs\n"
|
|
<< " --grid <cols>x<rows> Set multiview grid (default 2x2, max 16 feeds)\n"
|
|
<< " --fps-cap <fps> Limit render loop FPS (default 60)\n"
|
|
<< " --present <mode> Present mode: fifo, mailbox, immediate (default mailbox)\n"
|
|
<< " --feed <idx> <kind> Set feed slot kind explicitly\n"
|
|
<< " --domain <idx> <path> Set MXL domain for feed slot\n"
|
|
<< " --flow <idx> <uuid> Set MXL flow UUID for feed slot\n"
|
|
<< " --version Print version\n"
|
|
<< " --help Print this help\n"
|
|
<< "Feed kinds: nosignal, none, fake, mxl, smpte, mxlsdk\n"
|
|
<< "Default: 2x2 grid, all slots nosignal"
|
|
<< std::endl;
|
|
}
|
|
|
|
ConfigParseResult parseAppConfig(
|
|
int argc,
|
|
char* argv[])
|
|
{
|
|
ConfigParseResult result;
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
if (std::string_view(argv[i]) != "--grid")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (i + 1 >= argc ||
|
|
!parseGrid(
|
|
argv[i + 1],
|
|
result.config.gridCols,
|
|
result.config.gridRows))
|
|
{
|
|
std::cerr << "--grid must be <cols>x<rows> with 1-"
|
|
<< MaxFeedCount << " total feeds"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
applyDefaultFeeds(result.config);
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
const std::string_view arg(argv[i]);
|
|
|
|
if (arg == "--help" || arg == "-h")
|
|
{
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 0;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
const std::string_view arg(argv[i]);
|
|
|
|
if (arg == "--version" || arg == "-v")
|
|
{
|
|
std::cout
|
|
<< AppName
|
|
<< " "
|
|
<< AppVersionMajor
|
|
<< "."
|
|
<< AppVersionMinor
|
|
<< "."
|
|
<< AppVersionPatch
|
|
<< std::endl;
|
|
|
|
result.shouldExit = true;
|
|
result.exitCode = 0;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
std::vector<std::string_view> feedArgs;
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
const std::string_view arg(argv[i]);
|
|
|
|
if (arg == "--no-fps")
|
|
{
|
|
result.config.logFps = false;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--perf")
|
|
{
|
|
result.config.logPerf = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--verbose")
|
|
{
|
|
result.config.verbose = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--grid")
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--fps-cap")
|
|
{
|
|
if (i + 1 >= argc)
|
|
{
|
|
std::cerr << "--fps-cap requires <fps>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t fpsCap = 0;
|
|
|
|
if (!parseUint32(argv[i + 1], fpsCap) ||
|
|
fpsCap == 0 || fpsCap > 240)
|
|
{
|
|
std::cerr << "--fps-cap must be 1-240"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.fpsCap = fpsCap;
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--present")
|
|
{
|
|
if (i + 1 >= argc)
|
|
{
|
|
std::cerr << "--present requires <mode>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
const std::optional<PresentModeConfig> mode =
|
|
parsePresentMode(argv[i + 1]);
|
|
|
|
if (!mode.has_value())
|
|
{
|
|
std::cerr
|
|
<< "--present must be fifo, mailbox, or immediate"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.presentMode = mode.value();
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--feed")
|
|
{
|
|
if (i + 2 >= argc)
|
|
{
|
|
std::cerr << "--feed requires <index> <kind>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t index = 0;
|
|
if (!parseFeedIndex(
|
|
argv[i + 1],
|
|
feedCount(result.config),
|
|
index))
|
|
{
|
|
std::cerr << "--feed index must be 0-"
|
|
<< (feedCount(result.config) - 1) << std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
const std::optional<FeedKind> kind =
|
|
parseFeedKind(argv[i + 2]);
|
|
|
|
if (!kind.has_value())
|
|
{
|
|
std::cerr << "Unknown feed kind: "
|
|
<< argv[i + 2] << std::endl;
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.feeds[index].kind = kind.value();
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--domain")
|
|
{
|
|
if (i + 2 >= argc)
|
|
{
|
|
std::cerr << "--domain requires <index> <path>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t index = 0;
|
|
if (!parseFeedIndex(
|
|
argv[i + 1],
|
|
feedCount(result.config),
|
|
index))
|
|
{
|
|
std::cerr << "--domain index must be 0-"
|
|
<< (feedCount(result.config) - 1) << std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.feeds[index].mxlDomain =
|
|
std::string(argv[i + 2]);
|
|
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--flow")
|
|
{
|
|
if (i + 2 >= argc)
|
|
{
|
|
std::cerr << "--flow requires <index> <uuid>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t index = 0;
|
|
if (!parseFeedIndex(
|
|
argv[i + 1],
|
|
feedCount(result.config),
|
|
index))
|
|
{
|
|
std::cerr << "--flow index must be 0-"
|
|
<< (feedCount(result.config) - 1) << std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.feeds[index].mxlFlowId =
|
|
std::string(argv[i + 2]);
|
|
result.config.feeds[index].kind =
|
|
FeedKind::MxlSdk;
|
|
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
feedArgs.push_back(arg);
|
|
}
|
|
|
|
if (feedArgs.size() > result.config.feeds.size())
|
|
{
|
|
std::cerr << "Too many feed arguments" << std::endl;
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
size_t feedArgIndex = 0;
|
|
|
|
for (uint32_t slot = 0;
|
|
slot < result.config.feeds.size() && feedArgIndex < feedArgs.size();
|
|
++slot)
|
|
{
|
|
if (result.config.feeds[slot].kind == FeedKind::MxlSdk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const std::optional<FeedKind> parsed =
|
|
parseFeedKind(feedArgs[feedArgIndex]);
|
|
|
|
if (!parsed.has_value())
|
|
{
|
|
std::cerr << "Unknown feed kind: "
|
|
<< feedArgs[feedArgIndex] << std::endl;
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.feeds[slot].kind = parsed.value();
|
|
++feedArgIndex;
|
|
}
|
|
|
|
if (feedArgIndex < feedArgs.size())
|
|
{
|
|
std::cerr << "Too many feed arguments for available slots"
|
|
<< std::endl;
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
return result;
|
|
}
|