initial commit
This commit is contained in:
+499
@@ -0,0 +1,499 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user