refactor(cli): migrate runtime and simulator parsers to CLI11
Replace hand-rolled argument parsing with CLI11-backed parse paths for streamer runtime config and simulator runtime options. Simulator help text is now generated from CLI11 app definitions to keep parser/help output in sync, while preserving legacy validation messages and exit semantics used by automation.
This commit is contained in:
+71
@@ -0,0 +1,71 @@
|
|||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Linker files
|
||||||
|
*.ilk
|
||||||
|
|
||||||
|
# Debugger Files
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
*.so.*
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# Build directories
|
||||||
|
build/
|
||||||
|
Build/
|
||||||
|
build-*/
|
||||||
|
|
||||||
|
# CMake generated files
|
||||||
|
CMakeFiles/
|
||||||
|
CMakeCache.txt
|
||||||
|
cmake_install.cmake
|
||||||
|
Makefile
|
||||||
|
install_manifest.txt
|
||||||
|
compile_commands.json
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# vcpkg
|
||||||
|
vcpkg_installed/
|
||||||
|
|
||||||
|
# debug information files
|
||||||
|
*.dwo
|
||||||
|
|
||||||
|
# test output & cache
|
||||||
|
Testing/
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# local evidence artifacts generated by standalone scripts
|
||||||
|
.sisyphus/
|
||||||
@@ -8,6 +8,10 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
|
if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/lib/CLI11/CMakeLists.txt")
|
||||||
|
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/lib/CLI11" "${CMAKE_CURRENT_BINARY_DIR}/vendor/cli11")
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(cppzmq QUIET)
|
find_package(cppzmq QUIET)
|
||||||
find_package(ZeroMQ QUIET)
|
find_package(ZeroMQ QUIET)
|
||||||
find_package(spdlog REQUIRED)
|
find_package(spdlog REQUIRED)
|
||||||
@@ -76,6 +80,10 @@ elseif (TARGET spdlog)
|
|||||||
list(APPEND CVMAP_STREAMER_LINK_DEPS spdlog)
|
list(APPEND CVMAP_STREAMER_LINK_DEPS spdlog)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (TARGET CLI11::CLI11)
|
||||||
|
list(APPEND CVMAP_STREAMER_LINK_DEPS CLI11::CLI11)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(cvmmap_streamer_common PUBLIC ${CVMAP_STREAMER_LINK_DEPS})
|
target_link_libraries(cvmmap_streamer_common PUBLIC ${CVMAP_STREAMER_LINK_DEPS})
|
||||||
|
|
||||||
function(add_cvmmap_binary target source)
|
function(add_cvmmap_binary target source)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <expected>
|
#include <expected>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include "cvmmap_streamer/ipc/contracts.hpp"
|
#include "cvmmap_streamer/ipc/contracts.hpp"
|
||||||
|
|
||||||
@@ -30,7 +31,23 @@ struct RuntimeConfig {
|
|||||||
std::uint32_t payload_size_bytes() const;
|
std::uint32_t payload_size_bytes() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **argv);
|
enum class ParseStatus {
|
||||||
void print_help();
|
Ok,
|
||||||
|
Help,
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
}
|
struct ParseResult {
|
||||||
|
ParseStatus status{ParseStatus::Ok};
|
||||||
|
RuntimeConfig config{};
|
||||||
|
std::string message{};
|
||||||
|
int exit_code{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc,
|
||||||
|
char **argv);
|
||||||
|
void print_help();
|
||||||
|
ParseResult parse_runtime_config_with_cli11(
|
||||||
|
int argc, char **argv, std::string_view executable_name = "cvmmap_sim");
|
||||||
|
|
||||||
|
} // namespace cvmmap_streamer::sim
|
||||||
|
|||||||
+249
-214
@@ -1,70 +1,131 @@
|
|||||||
#include "cvmmap_streamer/config/runtime_config.hpp"
|
#include "cvmmap_streamer/config/runtime_config.hpp"
|
||||||
|
|
||||||
|
#include <CLI/CLI.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace cvmmap_streamer {
|
namespace cvmmap_streamer {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::expected<std::string_view, std::string>
|
std::string trim_copy(std::string value) {
|
||||||
next_value(int argc, char **argv, int &index, std::string_view flag_name) {
|
auto not_space = [](unsigned char c) {
|
||||||
if (index + 1 >= argc) {
|
return c != ' ' && c != '\t' && c != '\n' && c != '\r';
|
||||||
return std::unexpected("missing value for " + std::string(flag_name));
|
};
|
||||||
|
while (!value.empty() &&
|
||||||
|
!not_space(static_cast<unsigned char>(value.front()))) {
|
||||||
|
value.erase(value.begin());
|
||||||
}
|
}
|
||||||
++index;
|
while (!value.empty() &&
|
||||||
return std::string_view{argv[index]};
|
!not_space(static_cast<unsigned char>(value.back()))) {
|
||||||
|
value.pop_back();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<std::uint32_t, std::string> parse_u32(std::string_view raw, std::string_view flag_name) {
|
std::string normalize_cli_error(std::string raw_message) {
|
||||||
|
if (raw_message.find("The following argument was not expected:") !=
|
||||||
|
std::string::npos) {
|
||||||
|
const auto pos = raw_message.find(':');
|
||||||
|
if (pos != std::string::npos && pos + 1 < raw_message.size()) {
|
||||||
|
return "unknown argument: " + trim_copy(raw_message.substr(pos + 1));
|
||||||
|
}
|
||||||
|
return "unknown argument";
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::array<std::string_view, 17> kFlags{"--codec",
|
||||||
|
"--run-mode",
|
||||||
|
"--shm-name",
|
||||||
|
"--zmq-endpoint",
|
||||||
|
"--rtmp-url",
|
||||||
|
"--rtmp-mode",
|
||||||
|
"--rtp-endpoint",
|
||||||
|
"--rtp-payload-type",
|
||||||
|
"--rtp-sdp",
|
||||||
|
"--sdp",
|
||||||
|
"--queue-size",
|
||||||
|
"--gop",
|
||||||
|
"--b-frames",
|
||||||
|
"--realtime-sync",
|
||||||
|
"--force-idr-on-reset",
|
||||||
|
"--ingest-max-frames",
|
||||||
|
"--ingest-idle-timeout-ms"};
|
||||||
|
|
||||||
|
if (raw_message.find("requires at least") != std::string::npos ||
|
||||||
|
raw_message.find("requires ") != std::string::npos) {
|
||||||
|
for (const auto flag : kFlags) {
|
||||||
|
if (raw_message.find(flag) != std::string::npos) {
|
||||||
|
return "missing value for " + std::string(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim_copy(std::move(raw_message));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::uint32_t, std::string>
|
||||||
|
parse_u32(std::string_view raw, std::string_view flag_name) {
|
||||||
std::uint32_t value{0};
|
std::uint32_t value{0};
|
||||||
const auto *begin = raw.data();
|
const auto *begin = raw.data();
|
||||||
const auto *end = raw.data() + raw.size();
|
const auto *end = raw.data() + raw.size();
|
||||||
const auto result = std::from_chars(begin, end, value, 10);
|
const auto result = std::from_chars(begin, end, value, 10);
|
||||||
if (result.ec != std::errc{} || result.ptr != end) {
|
if (result.ec != std::errc{} || result.ptr != end) {
|
||||||
return std::unexpected("invalid value for " + std::string(flag_name) + ": '" + std::string(raw) + "'");
|
return std::unexpected("invalid value for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<std::uint16_t, std::string> parse_u16(std::string_view raw, std::string_view flag_name) {
|
std::expected<std::uint16_t, std::string>
|
||||||
|
parse_u16(std::string_view raw, std::string_view flag_name) {
|
||||||
std::uint16_t value{0};
|
std::uint16_t value{0};
|
||||||
const auto *begin = raw.data();
|
const auto *begin = raw.data();
|
||||||
const auto *end = raw.data() + raw.size();
|
const auto *end = raw.data() + raw.size();
|
||||||
const auto result = std::from_chars(begin, end, value, 10);
|
const auto result = std::from_chars(begin, end, value, 10);
|
||||||
if (result.ec != std::errc{} || result.ptr != end) {
|
if (result.ec != std::errc{} || result.ptr != end) {
|
||||||
return std::unexpected("invalid value for " + std::string(flag_name) + ": '" + std::string(raw) + "'");
|
return std::unexpected("invalid value for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<std::size_t, std::string> parse_size(std::string_view raw, std::string_view flag_name) {
|
std::expected<std::size_t, std::string> parse_size(std::string_view raw,
|
||||||
|
std::string_view flag_name) {
|
||||||
unsigned long long parsed{0};
|
unsigned long long parsed{0};
|
||||||
const auto *begin = raw.data();
|
const auto *begin = raw.data();
|
||||||
const auto *end = raw.data() + raw.size();
|
const auto *end = raw.data() + raw.size();
|
||||||
const auto result = std::from_chars(begin, end, parsed, 10);
|
const auto result = std::from_chars(begin, end, parsed, 10);
|
||||||
if (result.ec != std::errc{} || result.ptr != end) {
|
if (result.ec != std::errc{} || result.ptr != end) {
|
||||||
return std::unexpected("invalid value for " + std::string(flag_name) + ": '" + std::string(raw) + "'");
|
return std::unexpected("invalid value for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
}
|
}
|
||||||
if (parsed > static_cast<unsigned long long>(std::numeric_limits<std::size_t>::max())) {
|
if (parsed > static_cast<unsigned long long>(
|
||||||
return std::unexpected("value out of range for " + std::string(flag_name) + ": '" + std::string(raw) + "'");
|
std::numeric_limits<std::size_t>::max())) {
|
||||||
|
return std::unexpected("value out of range for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
}
|
}
|
||||||
return static_cast<std::size_t>(parsed);
|
return static_cast<std::size_t>(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<bool, std::string> parse_bool(std::string_view raw, std::string_view flag_name) {
|
std::expected<bool, std::string> parse_bool(std::string_view raw,
|
||||||
|
std::string_view flag_name) {
|
||||||
if (raw == "true" || raw == "1") {
|
if (raw == "true" || raw == "1") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (raw == "false" || raw == "0") {
|
if (raw == "false" || raw == "0") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return std::unexpected(
|
return std::unexpected("invalid value for " + std::string(flag_name) + ": '" +
|
||||||
"invalid value for " + std::string(flag_name) + ": '" + std::string(raw) + "' (expected: true|false|1|0)");
|
std::string(raw) + "' (expected: true|false|1|0)");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<CodecType, std::string> parse_codec(std::string_view raw) {
|
std::expected<CodecType, std::string> parse_codec(std::string_view raw) {
|
||||||
@@ -74,7 +135,8 @@ namespace {
|
|||||||
if (raw == "h265") {
|
if (raw == "h265") {
|
||||||
return CodecType::H265;
|
return CodecType::H265;
|
||||||
}
|
}
|
||||||
return std::unexpected("invalid codec: '" + std::string(raw) + "' (expected: h264|h265)");
|
return std::unexpected("invalid codec: '" + std::string(raw) +
|
||||||
|
"' (expected: h264|h265)");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<RunMode, std::string> parse_run_mode(std::string_view raw) {
|
std::expected<RunMode, std::string> parse_run_mode(std::string_view raw) {
|
||||||
@@ -84,7 +146,8 @@ namespace {
|
|||||||
if (raw == "ingest") {
|
if (raw == "ingest") {
|
||||||
return RunMode::Ingest;
|
return RunMode::Ingest;
|
||||||
}
|
}
|
||||||
return std::unexpected("invalid run mode: '" + std::string(raw) + "' (expected: pipeline|ingest)");
|
return std::unexpected("invalid run mode: '" + std::string(raw) +
|
||||||
|
"' (expected: pipeline|ingest)");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<RtmpMode, std::string> parse_rtmp_mode(std::string_view raw) {
|
std::expected<RtmpMode, std::string> parse_rtmp_mode(std::string_view raw) {
|
||||||
@@ -94,23 +157,29 @@ namespace {
|
|||||||
if (raw == "domestic") {
|
if (raw == "domestic") {
|
||||||
return RtmpMode::Domestic;
|
return RtmpMode::Domestic;
|
||||||
}
|
}
|
||||||
return std::unexpected("invalid rtmp mode: '" + std::string(raw) + "' (expected: enhanced|domestic)");
|
return std::unexpected("invalid rtmp mode: '" + std::string(raw) +
|
||||||
|
"' (expected: enhanced|domestic)");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<std::pair<std::string, std::uint16_t>, std::string> parse_rtp_endpoint(std::string_view endpoint) {
|
std::expected<std::pair<std::string, std::uint16_t>, std::string>
|
||||||
|
parse_rtp_endpoint(std::string_view endpoint) {
|
||||||
if (endpoint.empty()) {
|
if (endpoint.empty()) {
|
||||||
return std::unexpected("invalid RTP config: --rtp-endpoint must not be empty");
|
return std::unexpected(
|
||||||
|
"invalid RTP config: --rtp-endpoint must not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto colon = endpoint.rfind(':');
|
const auto colon = endpoint.rfind(':');
|
||||||
if (colon == std::string_view::npos || colon == 0 || colon + 1 >= endpoint.size()) {
|
if (colon == std::string_view::npos || colon == 0 ||
|
||||||
return std::unexpected("invalid RTP config: --rtp-endpoint must be in '<host>:<port>' format");
|
colon + 1 >= endpoint.size()) {
|
||||||
|
return std::unexpected(
|
||||||
|
"invalid RTP config: --rtp-endpoint must be in '<host>:<port>' format");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto host = endpoint.substr(0, colon);
|
const auto host = endpoint.substr(0, colon);
|
||||||
const auto port = endpoint.substr(colon + 1);
|
const auto port = endpoint.substr(colon + 1);
|
||||||
if (host.empty()) {
|
if (host.empty()) {
|
||||||
return std::unexpected("invalid RTP config: --rtp-endpoint host must not be empty");
|
return std::unexpected(
|
||||||
|
"invalid RTP config: --rtp-endpoint host must not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto parsed_port = parse_u16(port, "--rtp-endpoint");
|
auto parsed_port = parse_u16(port, "--rtp-endpoint");
|
||||||
@@ -118,17 +187,16 @@ namespace {
|
|||||||
return std::unexpected(parsed_port.error());
|
return std::unexpected(parsed_port.error());
|
||||||
}
|
}
|
||||||
if (*parsed_port == 0) {
|
if (*parsed_port == 0) {
|
||||||
return std::unexpected("invalid RTP config: --rtp-endpoint port must be in range [1,65535]");
|
return std::unexpected(
|
||||||
|
"invalid RTP config: --rtp-endpoint port must be in range [1,65535]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::pair{std::string(host), *parsed_port};
|
return std::pair{std::string(host), *parsed_port};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
RuntimeConfig RuntimeConfig::defaults() {
|
RuntimeConfig RuntimeConfig::defaults() { return RuntimeConfig{}; }
|
||||||
return RuntimeConfig{};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string_view to_string(CodecType codec) {
|
std::string_view to_string(CodecType codec) {
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
@@ -163,302 +231,256 @@ std::string_view to_string(RtmpMode mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **argv) {
|
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc,
|
||||||
|
char **argv) {
|
||||||
RuntimeConfig config = RuntimeConfig::defaults();
|
RuntimeConfig config = RuntimeConfig::defaults();
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
std::string codec_raw;
|
||||||
const std::string_view arg{argv[i]};
|
std::string run_mode_raw;
|
||||||
|
std::string shm_name_raw;
|
||||||
|
std::string zmq_endpoint_raw;
|
||||||
|
std::vector<std::string> rtmp_urls_raw;
|
||||||
|
std::string rtmp_mode_raw;
|
||||||
|
std::string rtp_endpoint_raw;
|
||||||
|
std::string rtp_payload_type_raw;
|
||||||
|
std::string rtp_sdp_raw;
|
||||||
|
std::string queue_size_raw;
|
||||||
|
std::string gop_raw;
|
||||||
|
std::string b_frames_raw;
|
||||||
|
std::string realtime_sync_raw;
|
||||||
|
std::string force_idr_on_reset_raw;
|
||||||
|
std::string ingest_max_frames_raw;
|
||||||
|
std::string ingest_idle_timeout_raw;
|
||||||
|
std::string ingest_consumer_delay_raw;
|
||||||
|
std::string snapshot_copy_delay_raw;
|
||||||
|
std::string emit_stall_raw;
|
||||||
|
|
||||||
if (arg == "--codec") {
|
bool rtmp_enabled{false};
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
bool rtp_enabled{false};
|
||||||
if (!raw) {
|
bool version_requested{false};
|
||||||
return std::unexpected(raw.error());
|
|
||||||
|
CLI::App app{"cvmmap-streamer runtime options"};
|
||||||
|
app.allow_extras(false);
|
||||||
|
app.set_help_flag("--help,-h", "show this message");
|
||||||
|
|
||||||
|
app.add_option("--codec", codec_raw);
|
||||||
|
app.add_option("--run-mode", run_mode_raw);
|
||||||
|
app.add_option("--shm-name", shm_name_raw);
|
||||||
|
app.add_option("--zmq-endpoint", zmq_endpoint_raw);
|
||||||
|
app.add_flag("--rtmp", rtmp_enabled);
|
||||||
|
app.add_option("--rtmp-url", rtmp_urls_raw);
|
||||||
|
app.add_option("--rtmp-mode", rtmp_mode_raw);
|
||||||
|
app.add_flag("--rtp", rtp_enabled);
|
||||||
|
app.add_option("--rtp-endpoint", rtp_endpoint_raw);
|
||||||
|
app.add_option("--rtp-payload-type", rtp_payload_type_raw);
|
||||||
|
auto *rtp_sdp = app.add_option("--rtp-sdp", rtp_sdp_raw);
|
||||||
|
app.add_option("--sdp", rtp_sdp_raw)->excludes(rtp_sdp);
|
||||||
|
app.add_option("--queue-size", queue_size_raw);
|
||||||
|
app.add_option("--gop", gop_raw);
|
||||||
|
app.add_option("--b-frames", b_frames_raw);
|
||||||
|
app.add_option("--realtime-sync", realtime_sync_raw);
|
||||||
|
app.add_option("--force-idr-on-reset", force_idr_on_reset_raw);
|
||||||
|
app.add_option("--ingest-max-frames", ingest_max_frames_raw);
|
||||||
|
app.add_option("--ingest-idle-timeout-ms", ingest_idle_timeout_raw);
|
||||||
|
app.add_option("--ingest-consumer-delay-ms", ingest_consumer_delay_raw);
|
||||||
|
app.add_option("--snapshot-copy-delay-us", snapshot_copy_delay_raw);
|
||||||
|
app.add_option("--emit-stall-ms", emit_stall_raw);
|
||||||
|
app.add_flag("--version", version_requested);
|
||||||
|
|
||||||
|
try {
|
||||||
|
app.parse(argc, argv);
|
||||||
|
} catch (const CLI::ParseError &e) {
|
||||||
|
return std::unexpected(normalize_cli_error(e.what()));
|
||||||
}
|
}
|
||||||
auto codec = parse_codec(*raw);
|
|
||||||
|
if (!codec_raw.empty()) {
|
||||||
|
auto codec = parse_codec(codec_raw);
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
return std::unexpected(codec.error());
|
return std::unexpected(codec.error());
|
||||||
}
|
}
|
||||||
config.codec = *codec;
|
config.codec = *codec;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--run-mode") {
|
if (!run_mode_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto run_mode = parse_run_mode(run_mode_raw);
|
||||||
if (!raw) {
|
if (!run_mode) {
|
||||||
return std::unexpected(raw.error());
|
return std::unexpected(run_mode.error());
|
||||||
}
|
}
|
||||||
auto mode = parse_run_mode(*raw);
|
config.run_mode = *run_mode;
|
||||||
if (!mode) {
|
|
||||||
return std::unexpected(mode.error());
|
|
||||||
}
|
|
||||||
config.run_mode = *mode;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--shm-name") {
|
if (!shm_name_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
config.input.shm_name = shm_name_raw;
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
config.input.shm_name = std::string(*value);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--zmq-endpoint") {
|
if (!zmq_endpoint_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
config.input.zmq_endpoint = zmq_endpoint_raw;
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
config.input.zmq_endpoint = std::string(*value);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--rtmp") {
|
config.outputs.rtmp.enabled = rtmp_enabled;
|
||||||
|
if (!rtmp_urls_raw.empty()) {
|
||||||
config.outputs.rtmp.enabled = true;
|
config.outputs.rtmp.enabled = true;
|
||||||
continue;
|
config.outputs.rtmp.urls = std::move(rtmp_urls_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--rtmp-url") {
|
if (!rtmp_mode_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto mode = parse_rtmp_mode(rtmp_mode_raw);
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
config.outputs.rtmp.enabled = true;
|
|
||||||
config.outputs.rtmp.urls.emplace_back(*value);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--rtmp-mode") {
|
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto mode = parse_rtmp_mode(*raw);
|
|
||||||
if (!mode) {
|
if (!mode) {
|
||||||
return std::unexpected(mode.error());
|
return std::unexpected(mode.error());
|
||||||
}
|
}
|
||||||
config.outputs.rtmp.mode = *mode;
|
config.outputs.rtmp.mode = *mode;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--rtp") {
|
config.outputs.rtp.enabled = rtp_enabled;
|
||||||
config.outputs.rtp.enabled = true;
|
if (!rtp_endpoint_raw.empty()) {
|
||||||
continue;
|
auto endpoint = parse_rtp_endpoint(rtp_endpoint_raw);
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--rtp-endpoint") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
auto endpoint = parse_rtp_endpoint(*value);
|
|
||||||
if (!endpoint) {
|
if (!endpoint) {
|
||||||
return std::unexpected(endpoint.error());
|
return std::unexpected(endpoint.error());
|
||||||
}
|
}
|
||||||
config.outputs.rtp.enabled = true;
|
config.outputs.rtp.enabled = true;
|
||||||
config.outputs.rtp.endpoint = std::string(*value);
|
config.outputs.rtp.endpoint = rtp_endpoint_raw;
|
||||||
config.outputs.rtp.host = endpoint->first;
|
config.outputs.rtp.host = endpoint->first;
|
||||||
config.outputs.rtp.port = endpoint->second;
|
config.outputs.rtp.port = endpoint->second;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--rtp-payload-type") {
|
if (!rtp_payload_type_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto value = parse_u32(rtp_payload_type_raw, "--rtp-payload-type");
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto value = parse_u32(*raw, arg);
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return std::unexpected(value.error());
|
return std::unexpected(value.error());
|
||||||
}
|
}
|
||||||
if (*value > std::numeric_limits<std::uint8_t>::max()) {
|
if (*value > std::numeric_limits<std::uint8_t>::max()) {
|
||||||
return std::unexpected("value out of range for --rtp-payload-type: '" + std::string(*raw) + "'");
|
return std::unexpected("value out of range for --rtp-payload-type: '" +
|
||||||
|
rtp_payload_type_raw + "'");
|
||||||
}
|
}
|
||||||
config.outputs.rtp.enabled = true;
|
config.outputs.rtp.enabled = true;
|
||||||
config.outputs.rtp.payload_type = static_cast<std::uint8_t>(*value);
|
config.outputs.rtp.payload_type = static_cast<std::uint8_t>(*value);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--rtp-sdp" || arg == "--sdp") {
|
if (!rtp_sdp_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
if (value->empty()) {
|
|
||||||
return std::unexpected("invalid RTP config: " + std::string(arg) + " must not be empty");
|
|
||||||
}
|
|
||||||
config.outputs.rtp.enabled = true;
|
config.outputs.rtp.enabled = true;
|
||||||
config.outputs.rtp.sdp_path = std::string(*value);
|
config.outputs.rtp.sdp_path = rtp_sdp_raw;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--queue-size") {
|
if (!queue_size_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed = parse_size(queue_size_raw, "--queue-size");
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_size(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.queue_size = *parsed;
|
config.latency.queue_size = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--gop") {
|
if (!gop_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(gop_raw, "--gop");
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.gop = *parsed;
|
config.latency.gop = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--b-frames") {
|
if (!b_frames_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(b_frames_raw, "--b-frames");
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.b_frames = *parsed;
|
config.latency.b_frames = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--realtime-sync") {
|
if (!realtime_sync_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed = parse_bool(realtime_sync_raw, "--realtime-sync");
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_bool(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.realtime_sync = *parsed;
|
config.latency.realtime_sync = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--force-idr-on-reset") {
|
if (!force_idr_on_reset_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed = parse_bool(force_idr_on_reset_raw, "--force-idr-on-reset");
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_bool(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.force_idr_on_reset = *parsed;
|
config.latency.force_idr_on_reset = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--ingest-max-frames") {
|
if (!ingest_max_frames_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(ingest_max_frames_raw, "--ingest-max-frames");
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.ingest_max_frames = *parsed;
|
config.latency.ingest_max_frames = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--ingest-idle-timeout-ms") {
|
if (!ingest_idle_timeout_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed =
|
||||||
if (!raw) {
|
parse_u32(ingest_idle_timeout_raw, "--ingest-idle-timeout-ms");
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.ingest_idle_timeout_ms = *parsed;
|
config.latency.ingest_idle_timeout_ms = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--ingest-consumer-delay-ms") {
|
if (!ingest_consumer_delay_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed =
|
||||||
if (!raw) {
|
parse_u32(ingest_consumer_delay_raw, "--ingest-consumer-delay-ms");
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.ingest_consumer_delay_ms = *parsed;
|
config.latency.ingest_consumer_delay_ms = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--snapshot-copy-delay-us") {
|
if (!snapshot_copy_delay_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed =
|
||||||
if (!raw) {
|
parse_u32(snapshot_copy_delay_raw, "--snapshot-copy-delay-us");
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.snapshot_copy_delay_us = *parsed;
|
config.latency.snapshot_copy_delay_us = *parsed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--emit-stall-ms") {
|
if (!emit_stall_raw.empty()) {
|
||||||
auto raw = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(emit_stall_raw, "--emit-stall-ms");
|
||||||
if (!raw) {
|
|
||||||
return std::unexpected(raw.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*raw, arg);
|
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return std::unexpected(parsed.error());
|
return std::unexpected(parsed.error());
|
||||||
}
|
}
|
||||||
config.latency.emit_stall_ms = *parsed;
|
config.latency.emit_stall_ms = *parsed;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--help" || arg == "-h" || arg == "--version") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::unexpected("unknown argument: " + std::string(arg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<void, std::string> validate_runtime_config(const RuntimeConfig &config) {
|
std::expected<void, std::string>
|
||||||
|
validate_runtime_config(const RuntimeConfig &config) {
|
||||||
if (config.input.shm_name.empty()) {
|
if (config.input.shm_name.empty()) {
|
||||||
return std::unexpected("invalid input config: --shm-name must not be empty");
|
return std::unexpected(
|
||||||
|
"invalid input config: --shm-name must not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.input.zmq_endpoint.empty()) {
|
if (config.input.zmq_endpoint.empty()) {
|
||||||
return std::unexpected("invalid input config: --zmq-endpoint must not be empty");
|
return std::unexpected(
|
||||||
|
"invalid input config: --zmq-endpoint must not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.outputs.rtmp.enabled && config.outputs.rtmp.urls.empty()) {
|
if (config.outputs.rtmp.enabled && config.outputs.rtmp.urls.empty()) {
|
||||||
return std::unexpected("invalid RTMP config: --rtmp requires at least one --rtmp-url");
|
return std::unexpected(
|
||||||
|
"invalid RTMP config: --rtmp requires at least one --rtmp-url");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &url : config.outputs.rtmp.urls) {
|
for (const auto &url : config.outputs.rtmp.urls) {
|
||||||
if (url.empty()) {
|
if (url.empty()) {
|
||||||
return std::unexpected("invalid RTMP config: --rtmp-url must not be empty");
|
return std::unexpected(
|
||||||
|
"invalid RTMP config: --rtmp-url must not be empty");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.outputs.rtmp.mode == RtmpMode::Domestic && config.codec != CodecType::H265) {
|
if (config.outputs.rtmp.mode == RtmpMode::Domestic &&
|
||||||
return std::unexpected(
|
config.codec != CodecType::H265) {
|
||||||
"invalid mode matrix: --rtmp-mode domestic requires --codec h265 (h264+domestic is unsupported)");
|
return std::unexpected("invalid mode matrix: --rtmp-mode domestic requires "
|
||||||
|
"--codec h265 (h264+domestic is unsupported)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.outputs.rtp.enabled) {
|
if (config.outputs.rtp.enabled) {
|
||||||
if (!config.outputs.rtp.endpoint || config.outputs.rtp.endpoint->empty()) {
|
if (!config.outputs.rtp.endpoint || config.outputs.rtp.endpoint->empty()) {
|
||||||
return std::unexpected("invalid RTP config: --rtp requires --rtp-endpoint");
|
return std::unexpected(
|
||||||
|
"invalid RTP config: --rtp requires --rtp-endpoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto endpoint_validation = parse_rtp_endpoint(*config.outputs.rtp.endpoint);
|
auto endpoint_validation = parse_rtp_endpoint(*config.outputs.rtp.endpoint);
|
||||||
@@ -466,13 +488,15 @@ std::expected<void, std::string> validate_runtime_config(const RuntimeConfig &co
|
|||||||
return std::unexpected(endpoint_validation.error());
|
return std::unexpected(endpoint_validation.error());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.outputs.rtp.payload_type < 96 || config.outputs.rtp.payload_type > 127) {
|
if (config.outputs.rtp.payload_type < 96 ||
|
||||||
return std::unexpected(
|
config.outputs.rtp.payload_type > 127) {
|
||||||
"invalid RTP config: --rtp-payload-type must be in dynamic range [96,127]");
|
return std::unexpected("invalid RTP config: --rtp-payload-type must be "
|
||||||
|
"in dynamic range [96,127]");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.outputs.rtp.sdp_path && config.outputs.rtp.sdp_path->empty()) {
|
if (config.outputs.rtp.sdp_path && config.outputs.rtp.sdp_path->empty()) {
|
||||||
return std::unexpected("invalid RTP config: --rtp-sdp/--sdp must not be empty");
|
return std::unexpected(
|
||||||
|
"invalid RTP config: --rtp-sdp/--sdp must not be empty");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,11 +509,13 @@ std::expected<void, std::string> validate_runtime_config(const RuntimeConfig &co
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.latency.b_frames > config.latency.gop) {
|
if (config.latency.b_frames > config.latency.gop) {
|
||||||
return std::unexpected("invalid latency config: --b-frames must be <= --gop");
|
return std::unexpected(
|
||||||
|
"invalid latency config: --b-frames must be <= --gop");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.latency.ingest_idle_timeout_ms == 0) {
|
if (config.latency.ingest_idle_timeout_ms == 0) {
|
||||||
return std::unexpected("invalid ingest config: --ingest-idle-timeout-ms must be >= 1");
|
return std::unexpected(
|
||||||
|
"invalid ingest config: --ingest-idle-timeout-ms must be >= 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@@ -505,20 +531,29 @@ std::string summarize_runtime_config(const RuntimeConfig &config) {
|
|||||||
ss << ", rtmp.mode=" << to_string(config.outputs.rtmp.mode);
|
ss << ", rtmp.mode=" << to_string(config.outputs.rtmp.mode);
|
||||||
ss << ", rtmp.urls=" << config.outputs.rtmp.urls.size();
|
ss << ", rtmp.urls=" << config.outputs.rtmp.urls.size();
|
||||||
ss << ", rtp.enabled=" << (config.outputs.rtp.enabled ? "true" : "false");
|
ss << ", rtp.enabled=" << (config.outputs.rtp.enabled ? "true" : "false");
|
||||||
ss << ", rtp.endpoint=" << (config.outputs.rtp.endpoint ? *config.outputs.rtp.endpoint : "<unset>");
|
ss << ", rtp.endpoint="
|
||||||
ss << ", rtp.payload_type=" << static_cast<unsigned>(config.outputs.rtp.payload_type);
|
<< (config.outputs.rtp.endpoint ? *config.outputs.rtp.endpoint
|
||||||
ss << ", rtp.sdp=" << (config.outputs.rtp.sdp_path ? *config.outputs.rtp.sdp_path : "<auto>");
|
: "<unset>");
|
||||||
|
ss << ", rtp.payload_type="
|
||||||
|
<< static_cast<unsigned>(config.outputs.rtp.payload_type);
|
||||||
|
ss << ", rtp.sdp="
|
||||||
|
<< (config.outputs.rtp.sdp_path ? *config.outputs.rtp.sdp_path : "<auto>");
|
||||||
ss << ", latency.queue_size=" << config.latency.queue_size;
|
ss << ", latency.queue_size=" << config.latency.queue_size;
|
||||||
ss << ", latency.gop=" << config.latency.gop;
|
ss << ", latency.gop=" << config.latency.gop;
|
||||||
ss << ", latency.b_frames=" << config.latency.b_frames;
|
ss << ", latency.b_frames=" << config.latency.b_frames;
|
||||||
ss << ", latency.realtime_sync=" << (config.latency.realtime_sync ? "true" : "false");
|
ss << ", latency.realtime_sync="
|
||||||
ss << ", latency.force_idr_on_reset=" << (config.latency.force_idr_on_reset ? "true" : "false");
|
<< (config.latency.realtime_sync ? "true" : "false");
|
||||||
|
ss << ", latency.force_idr_on_reset="
|
||||||
|
<< (config.latency.force_idr_on_reset ? "true" : "false");
|
||||||
ss << ", latency.ingest_max_frames=" << config.latency.ingest_max_frames;
|
ss << ", latency.ingest_max_frames=" << config.latency.ingest_max_frames;
|
||||||
ss << ", latency.ingest_idle_timeout_ms=" << config.latency.ingest_idle_timeout_ms;
|
ss << ", latency.ingest_idle_timeout_ms="
|
||||||
ss << ", latency.ingest_consumer_delay_ms=" << config.latency.ingest_consumer_delay_ms;
|
<< config.latency.ingest_idle_timeout_ms;
|
||||||
ss << ", latency.snapshot_copy_delay_us=" << config.latency.snapshot_copy_delay_us;
|
ss << ", latency.ingest_consumer_delay_ms="
|
||||||
|
<< config.latency.ingest_consumer_delay_ms;
|
||||||
|
ss << ", latency.snapshot_copy_delay_us="
|
||||||
|
<< config.latency.snapshot_copy_delay_us;
|
||||||
ss << ", latency.emit_stall_ms=" << config.latency.emit_stall_ms;
|
ss << ", latency.emit_stall_ms=" << config.latency.emit_stall_ms;
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace cvmmap_streamer
|
||||||
|
|||||||
+320
-200
@@ -1,8 +1,13 @@
|
|||||||
#include "cvmmap_streamer/sim/options.hpp"
|
#include "cvmmap_streamer/sim/options.hpp"
|
||||||
|
|
||||||
|
#include <CLI/CLI.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
@@ -11,223 +16,93 @@ namespace cvmmap_streamer::sim {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::expected<std::string_view, std::string> next_value(int argc, char **argv, int &index, std::string_view flag_name) {
|
std::string trim_copy(std::string value) {
|
||||||
if (index + 1 >= argc) {
|
auto not_space = [](unsigned char c) {
|
||||||
return std::unexpected("missing value for " + std::string(flag_name));
|
return c != ' ' && c != '\t' && c != '\n' && c != '\r';
|
||||||
|
};
|
||||||
|
while (!value.empty() &&
|
||||||
|
!not_space(static_cast<unsigned char>(value.front()))) {
|
||||||
|
value.erase(value.begin());
|
||||||
}
|
}
|
||||||
++index;
|
while (!value.empty() &&
|
||||||
return std::string_view{argv[index]};
|
!not_space(static_cast<unsigned char>(value.back()))) {
|
||||||
}
|
value.pop_back();
|
||||||
|
|
||||||
std::expected<std::uint32_t, std::string> parse_u32(std::string_view raw, std::string_view flag_name) {
|
|
||||||
std::uint32_t value{0};
|
|
||||||
const auto result = std::from_chars(raw.data(), raw.data() + raw.size(), value, 10);
|
|
||||||
if (result.ec != std::errc{} || result.ptr != raw.data() + raw.size()) {
|
|
||||||
return std::unexpected("invalid value for " + std::string(flag_name) + ": '" + std::string(raw) + "'");
|
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<std::uint16_t, std::string> parse_u16(std::string_view raw, std::string_view flag_name) {
|
std::string normalize_cli_error(std::string raw_message) {
|
||||||
std::uint16_t value{0};
|
if (raw_message.find("The following argument was not expected:") !=
|
||||||
const auto result = std::from_chars(raw.data(), raw.data() + raw.size(), value, 10);
|
std::string::npos) {
|
||||||
if (result.ec != std::errc{} || result.ptr != raw.data() + raw.size()) {
|
const auto pos = raw_message.find(':');
|
||||||
return std::unexpected("invalid value for " + std::string(flag_name) + ": '" + std::string(raw) + "'");
|
if (pos != std::string::npos && pos + 1 < raw_message.size()) {
|
||||||
|
return "unknown argument: '" + trim_copy(raw_message.substr(pos + 1)) +
|
||||||
|
"'";
|
||||||
}
|
}
|
||||||
return value;
|
return "unknown argument";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr std::array<std::string_view, 12> kFlags{"--frames",
|
||||||
|
"--fps",
|
||||||
|
"--width",
|
||||||
|
"--height",
|
||||||
|
"--emit-reset-at",
|
||||||
|
"--emit-reset-every",
|
||||||
|
"--switch-format-at",
|
||||||
|
"--switch-width",
|
||||||
|
"--switch-height",
|
||||||
|
"--label",
|
||||||
|
"--shm-name",
|
||||||
|
"--zmq-endpoint"};
|
||||||
|
|
||||||
|
if (raw_message.find("requires at least") != std::string::npos ||
|
||||||
|
raw_message.find("requires ") != std::string::npos) {
|
||||||
|
for (const auto flag : kFlags) {
|
||||||
|
if (raw_message.find(flag) != std::string::npos) {
|
||||||
|
return "missing value for " + std::string(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint32_t RuntimeConfig::payload_size_bytes() const {
|
|
||||||
const auto width64 = static_cast<std::uint64_t>(width);
|
|
||||||
const auto height64 = static_cast<std::uint64_t>(height);
|
|
||||||
const auto channels64 = static_cast<std::uint64_t>(channels);
|
|
||||||
return static_cast<std::uint32_t>(width64 * height64 * channels64);
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_help() {
|
|
||||||
constexpr std::array<std::string_view, 17> kHelpLines{
|
|
||||||
"cvmmap_sim",
|
|
||||||
"Usage:",
|
|
||||||
" cvmmap_sim [options]",
|
|
||||||
"",
|
|
||||||
"Required simulation controls:",
|
|
||||||
" --frames <n> total frames to emit (default: 360)",
|
|
||||||
" --fps <n> deterministic frame pacing, 0 disables sleep (default: 60)",
|
|
||||||
" --width <px> frame width (default: 64)",
|
|
||||||
" --height <px> frame height (default: 48)",
|
|
||||||
" --emit-reset-at <n> emit MODULE_STATUS_STREAM_RESET at frame_count n",
|
|
||||||
" --emit-reset-every <n> emit MODULE_STATUS_STREAM_RESET each n frames",
|
|
||||||
" --switch-format-at <n> switch metadata frame format at frame_count n",
|
|
||||||
" --switch-width <px> width after format switch (default: --width)",
|
|
||||||
" --switch-height <px> height after format switch (default: --height)",
|
|
||||||
"",
|
|
||||||
"Optional:",
|
|
||||||
" --label <name> --shm-name <name> --zmq-endpoint <endpoint>"};
|
|
||||||
|
|
||||||
for (const auto &line : kHelpLines) {
|
|
||||||
spdlog::info("{}", line);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **argv) {
|
return trim_copy(std::move(raw_message));
|
||||||
RuntimeConfig config{};
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
|
||||||
const std::string_view arg{argv[i]};
|
|
||||||
|
|
||||||
if (arg == "--frames") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.frames = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--fps") {
|
std::expected<std::uint32_t, std::string>
|
||||||
auto value = next_value(argc, argv, i, arg);
|
parse_u32(std::string_view raw, std::string_view flag_name) {
|
||||||
if (!value) {
|
unsigned long parsed{0};
|
||||||
return std::unexpected(value.error());
|
const auto *begin = raw.data();
|
||||||
|
const auto *end = raw.data() + raw.size();
|
||||||
|
const auto result = std::from_chars(begin, end, parsed, 10);
|
||||||
|
if (result.ec != std::errc{} || result.ptr != end) {
|
||||||
|
return std::unexpected("invalid value for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
}
|
}
|
||||||
auto parsed = parse_u32(*value, arg);
|
if (parsed > std::numeric_limits<std::uint32_t>::max()) {
|
||||||
if (!parsed) {
|
return std::unexpected("value out of range for " + std::string(flag_name) +
|
||||||
return std::unexpected(parsed.error());
|
": '" + std::string(raw) + "'");
|
||||||
}
|
}
|
||||||
config.fps = *parsed;
|
return static_cast<std::uint32_t>(parsed);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "--width") {
|
std::expected<std::uint16_t, std::string>
|
||||||
auto value = next_value(argc, argv, i, arg);
|
parse_u16(std::string_view raw, std::string_view flag_name) {
|
||||||
if (!value) {
|
unsigned int parsed{0};
|
||||||
return std::unexpected(value.error());
|
const auto *begin = raw.data();
|
||||||
|
const auto *end = raw.data() + raw.size();
|
||||||
|
const auto result = std::from_chars(begin, end, parsed, 10);
|
||||||
|
if (result.ec != std::errc{} || result.ptr != end) {
|
||||||
|
return std::unexpected("invalid value for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
}
|
}
|
||||||
auto parsed = parse_u16(*value, arg);
|
if (parsed > std::numeric_limits<std::uint16_t>::max()) {
|
||||||
if (!parsed) {
|
return std::unexpected("value out of range for " + std::string(flag_name) +
|
||||||
return std::unexpected(parsed.error());
|
": '" + std::string(raw) + "'");
|
||||||
}
|
}
|
||||||
config.width = *parsed;
|
return static_cast<std::uint16_t>(parsed);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--height") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u16(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.height = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--emit-reset-at") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.emit_reset_at = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--emit-reset-every") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.emit_reset_every = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--switch-format-at") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.switch_format_at = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--switch-width") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u16(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.switch_width = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--switch-height") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
auto parsed = parse_u16(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.switch_height = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--label") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
config.label = std::string(*value);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--shm-name") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
config.shm_name = std::string(*value);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--zmq-endpoint") {
|
|
||||||
auto value = next_value(argc, argv, i, arg);
|
|
||||||
if (!value) {
|
|
||||||
return std::unexpected(value.error());
|
|
||||||
}
|
|
||||||
config.zmq_endpoint = std::string(*value);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--help" || arg == "-h") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::unexpected("unknown argument: '" + std::string(arg) + "'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string>
|
||||||
|
validate_runtime_config(const RuntimeConfig &config) {
|
||||||
if (config.frames == 0) {
|
if (config.frames == 0) {
|
||||||
return std::unexpected("--frames must be > 0");
|
return std::unexpected("--frames must be > 0");
|
||||||
}
|
}
|
||||||
@@ -271,8 +146,10 @@ std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **
|
|||||||
const auto payload_size = static_cast<std::uint64_t>(config.width) *
|
const auto payload_size = static_cast<std::uint64_t>(config.width) *
|
||||||
static_cast<std::uint64_t>(config.height) *
|
static_cast<std::uint64_t>(config.height) *
|
||||||
static_cast<std::uint64_t>(config.channels);
|
static_cast<std::uint64_t>(config.channels);
|
||||||
if (payload_size > static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
if (payload_size >
|
||||||
return std::unexpected("computed payload size exceeds cv-mmap frame_info.buffer_size range");
|
static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
||||||
|
return std::unexpected(
|
||||||
|
"computed payload size exceeds cv-mmap frame_info.buffer_size range");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto switched_width = config.switch_width.value_or(config.width);
|
const auto switched_width = config.switch_width.value_or(config.width);
|
||||||
@@ -281,11 +158,254 @@ std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **
|
|||||||
static_cast<std::uint64_t>(switched_width) *
|
static_cast<std::uint64_t>(switched_width) *
|
||||||
static_cast<std::uint64_t>(switched_height) *
|
static_cast<std::uint64_t>(switched_height) *
|
||||||
static_cast<std::uint64_t>(config.channels);
|
static_cast<std::uint64_t>(config.channels);
|
||||||
if (switched_payload_size > static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
if (switched_payload_size >
|
||||||
return std::unexpected("computed switched payload size exceeds cv-mmap frame_info.buffer_size range");
|
static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
||||||
|
return std::unexpected("computed switched payload size exceeds cv-mmap "
|
||||||
|
"frame_info.buffer_size range");
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string build_help_text(std::string_view executable_name = "cvmmap_sim") {
|
||||||
|
RuntimeConfig defaults{};
|
||||||
|
std::string frames_raw;
|
||||||
|
std::string fps_raw;
|
||||||
|
std::string width_raw;
|
||||||
|
std::string height_raw;
|
||||||
|
std::string emit_reset_at_raw;
|
||||||
|
std::string emit_reset_every_raw;
|
||||||
|
std::string switch_format_at_raw;
|
||||||
|
std::string switch_width_raw;
|
||||||
|
std::string switch_height_raw;
|
||||||
|
std::string label_raw;
|
||||||
|
std::string shm_name_raw;
|
||||||
|
std::string zmq_endpoint_raw;
|
||||||
|
|
||||||
|
CLI::App app{"cv-mmap deterministic frame simulator"};
|
||||||
|
app.name(std::string(executable_name));
|
||||||
|
app.allow_extras(false);
|
||||||
|
app.set_help_flag("--help,-h", "show this message");
|
||||||
|
|
||||||
|
app.add_option("--frames", frames_raw, "total frames to emit (default: 360)");
|
||||||
|
app.add_option("--fps", fps_raw,
|
||||||
|
"deterministic frame pacing, 0 disables sleep (default: 60)");
|
||||||
|
app.add_option("--width", width_raw, "frame width (default: 64)");
|
||||||
|
app.add_option("--height", height_raw, "frame height (default: 48)");
|
||||||
|
app.add_option("--emit-reset-at", emit_reset_at_raw,
|
||||||
|
"emit MODULE_STATUS_STREAM_RESET at frame_count n");
|
||||||
|
app.add_option("--emit-reset-every", emit_reset_every_raw,
|
||||||
|
"emit MODULE_STATUS_STREAM_RESET each n frames");
|
||||||
|
app.add_option("--switch-format-at", switch_format_at_raw,
|
||||||
|
"switch metadata frame format at frame_count n");
|
||||||
|
app.add_option("--switch-width", switch_width_raw,
|
||||||
|
"width after format switch (default: --width)");
|
||||||
|
app.add_option("--switch-height", switch_height_raw,
|
||||||
|
"height after format switch (default: --height)");
|
||||||
|
app.add_option("--label", label_raw, "stream label (<=24 bytes)")
|
||||||
|
->default_val(defaults.label);
|
||||||
|
app.add_option("--shm-name", shm_name_raw, "POSIX shm object name")
|
||||||
|
->default_val(defaults.shm_name);
|
||||||
|
app.add_option("--zmq-endpoint", zmq_endpoint_raw, "ZMQ pub endpoint")
|
||||||
|
->default_val(defaults.zmq_endpoint);
|
||||||
|
|
||||||
|
return app.help();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::uint32_t RuntimeConfig::payload_size_bytes() const {
|
||||||
|
const auto width64 = static_cast<std::uint64_t>(width);
|
||||||
|
const auto height64 = static_cast<std::uint64_t>(height);
|
||||||
|
const auto channels64 = static_cast<std::uint64_t>(channels);
|
||||||
|
return static_cast<std::uint32_t>(width64 * height64 * channels64);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_help() {
|
||||||
|
std::istringstream in(build_help_text());
|
||||||
|
for (std::string line; std::getline(in, line);) {
|
||||||
|
spdlog::info("{}", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseResult parse_runtime_config_with_cli11(int argc, char **argv,
|
||||||
|
std::string_view executable_name) {
|
||||||
|
RuntimeConfig config{};
|
||||||
|
|
||||||
|
std::string frames_raw;
|
||||||
|
std::string fps_raw;
|
||||||
|
std::string width_raw;
|
||||||
|
std::string height_raw;
|
||||||
|
std::string emit_reset_at_raw;
|
||||||
|
std::string emit_reset_every_raw;
|
||||||
|
std::string switch_format_at_raw;
|
||||||
|
std::string switch_width_raw;
|
||||||
|
std::string switch_height_raw;
|
||||||
|
std::string label_raw;
|
||||||
|
std::string shm_name_raw;
|
||||||
|
std::string zmq_endpoint_raw;
|
||||||
|
|
||||||
|
CLI::App app{"cv-mmap deterministic frame simulator"};
|
||||||
|
app.name(std::string(executable_name));
|
||||||
|
app.allow_extras(false);
|
||||||
|
app.set_help_flag("--help,-h", "show this message");
|
||||||
|
|
||||||
|
app.add_option("--frames", frames_raw, "total frames to emit (default: 360)");
|
||||||
|
app.add_option("--fps", fps_raw,
|
||||||
|
"deterministic frame pacing, 0 disables sleep (default: 60)");
|
||||||
|
app.add_option("--width", width_raw, "frame width (default: 64)");
|
||||||
|
app.add_option("--height", height_raw, "frame height (default: 48)");
|
||||||
|
app.add_option("--emit-reset-at", emit_reset_at_raw,
|
||||||
|
"emit MODULE_STATUS_STREAM_RESET at frame_count n");
|
||||||
|
app.add_option("--emit-reset-every", emit_reset_every_raw,
|
||||||
|
"emit MODULE_STATUS_STREAM_RESET each n frames");
|
||||||
|
app.add_option("--switch-format-at", switch_format_at_raw,
|
||||||
|
"switch metadata frame format at frame_count n");
|
||||||
|
app.add_option("--switch-width", switch_width_raw,
|
||||||
|
"width after format switch (default: --width)");
|
||||||
|
app.add_option("--switch-height", switch_height_raw,
|
||||||
|
"height after format switch (default: --height)");
|
||||||
|
app.add_option("--label", label_raw, "stream label (<=24 bytes)");
|
||||||
|
app.add_option("--shm-name", shm_name_raw, "POSIX shm object name");
|
||||||
|
app.add_option("--zmq-endpoint", zmq_endpoint_raw, "ZMQ pub endpoint");
|
||||||
|
|
||||||
|
if (argc <= 1) {
|
||||||
|
return ParseResult{
|
||||||
|
.status = ParseStatus::Help, .message = app.help(), .exit_code = 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
app.parse(argc, argv);
|
||||||
|
} catch (const CLI::CallForHelp &) {
|
||||||
|
return ParseResult{
|
||||||
|
.status = ParseStatus::Help, .message = app.help(), .exit_code = 0};
|
||||||
|
} catch (const CLI::ParseError &e) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = normalize_cli_error(e.what()),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frames_raw.empty()) {
|
||||||
|
auto parsed = parse_u32(frames_raw, "--frames");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.frames = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fps_raw.empty()) {
|
||||||
|
auto parsed = parse_u32(fps_raw, "--fps");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.fps = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!width_raw.empty()) {
|
||||||
|
auto parsed = parse_u16(width_raw, "--width");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.width = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!height_raw.empty()) {
|
||||||
|
auto parsed = parse_u16(height_raw, "--height");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.height = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emit_reset_at_raw.empty()) {
|
||||||
|
auto parsed = parse_u32(emit_reset_at_raw, "--emit-reset-at");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.emit_reset_at = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emit_reset_every_raw.empty()) {
|
||||||
|
auto parsed = parse_u32(emit_reset_every_raw, "--emit-reset-every");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.emit_reset_every = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!switch_format_at_raw.empty()) {
|
||||||
|
auto parsed = parse_u32(switch_format_at_raw, "--switch-format-at");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.switch_format_at = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!switch_width_raw.empty()) {
|
||||||
|
auto parsed = parse_u16(switch_width_raw, "--switch-width");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.switch_width = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!switch_height_raw.empty()) {
|
||||||
|
auto parsed = parse_u16(switch_height_raw, "--switch-height");
|
||||||
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.switch_height = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!label_raw.empty()) {
|
||||||
|
config.label = label_raw;
|
||||||
|
}
|
||||||
|
if (!shm_name_raw.empty()) {
|
||||||
|
config.shm_name = shm_name_raw;
|
||||||
|
}
|
||||||
|
if (!zmq_endpoint_raw.empty()) {
|
||||||
|
config.zmq_endpoint = zmq_endpoint_raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto validation = validate_runtime_config(config);
|
||||||
|
if (!validation) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = validation.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult{
|
||||||
|
.status = ParseStatus::Ok, .config = config, .exit_code = 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc,
|
||||||
|
char **argv) {
|
||||||
|
auto parsed = parse_runtime_config_with_cli11(argc, argv);
|
||||||
|
if (parsed.status == ParseStatus::Ok) {
|
||||||
|
return parsed.config;
|
||||||
|
}
|
||||||
|
if (!parsed.message.empty()) {
|
||||||
|
return std::unexpected(parsed.message);
|
||||||
|
}
|
||||||
|
return std::unexpected("failed to parse runtime options");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cvmmap_streamer::sim
|
||||||
|
|||||||
Reference in New Issue
Block a user