refactor(core): remove dead simulator stubs and rename pipeline runtime
This commit is contained in:
+1
-2
@@ -36,12 +36,11 @@ add_library(cvmmap_streamer_common STATIC
|
|||||||
src/core/frame_source.cpp
|
src/core/frame_source.cpp
|
||||||
src/core/ingest_runtime.cpp
|
src/core/ingest_runtime.cpp
|
||||||
src/ipc/contracts.cpp
|
src/ipc/contracts.cpp
|
||||||
src/ipc/ipc_stub.cpp
|
|
||||||
src/sim/wire.cpp
|
src/sim/wire.cpp
|
||||||
lib/cvmmap-client-cpp/app_cvmmap_client.cpp
|
lib/cvmmap-client-cpp/app_cvmmap_client.cpp
|
||||||
lib/cvmmap-client-cpp/app_cvmmap_parser.cpp
|
lib/cvmmap-client-cpp/app_cvmmap_parser.cpp
|
||||||
src/metrics/latency_tracker.cpp
|
src/metrics/latency_tracker.cpp
|
||||||
src/pipeline/pipeline_stub.cpp
|
src/pipeline/pipeline_runtime.cpp
|
||||||
src/protocol/rtmp_publisher.cpp
|
src/protocol/rtmp_publisher.cpp
|
||||||
src/protocol/rtp_publisher.cpp)
|
src/protocol/rtp_publisher.cpp)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
namespace cvmmap_streamer {
|
namespace cvmmap_streamer {
|
||||||
|
|
||||||
void print_help(std::string_view executable);
|
void print_help(std::string_view executable);
|
||||||
bool has_help_flag(int argc, char **argv);
|
bool has_help_flag(int argc, char **argv);
|
||||||
std::size_t sample_ipc_payload_size();
|
|
||||||
void pipeline_tick();
|
void pipeline_tick();
|
||||||
void protocol_step();
|
void protocol_step();
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <expected>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "cvmmap_streamer/ipc/contracts.hpp"
|
|
||||||
|
|
||||||
namespace cvmmap_streamer::sim {
|
|
||||||
|
|
||||||
struct RuntimeConfig {
|
|
||||||
std::uint32_t frames{360};
|
|
||||||
std::uint32_t fps{60};
|
|
||||||
std::uint16_t width{64};
|
|
||||||
std::uint16_t height{48};
|
|
||||||
std::optional<std::uint32_t> emit_reset_at{};
|
|
||||||
std::optional<std::uint32_t> emit_reset_every{};
|
|
||||||
std::optional<std::uint32_t> switch_format_at{};
|
|
||||||
std::optional<std::uint16_t> switch_width{};
|
|
||||||
std::optional<std::uint16_t> switch_height{};
|
|
||||||
std::string label{"sim"};
|
|
||||||
std::string shm_name{"cvmmap_sim"};
|
|
||||||
std::string zmq_endpoint{"ipc:///tmp/cvmmap_sim"};
|
|
||||||
std::uint8_t channels{3};
|
|
||||||
ipc::Depth depth{ipc::Depth::U8};
|
|
||||||
ipc::PixelFormat pixel_format{ipc::PixelFormat::BGR};
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
std::uint32_t payload_size_bytes() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ParseStatus {
|
|
||||||
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
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#include "cvmmap_streamer/ipc/contracts.hpp"
|
|
||||||
|
|
||||||
namespace cvmmap_streamer {
|
|
||||||
|
|
||||||
std::size_t sample_ipc_payload_size() {
|
|
||||||
return ipc::kShmPayloadOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
#include "cvmmap_streamer/common.h"
|
|
||||||
#include "cvmmap_streamer/ipc/contracts.hpp"
|
|
||||||
#include "cvmmap_streamer/sim/options.hpp"
|
|
||||||
#include "cvmmap_streamer/sim/wire.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <expected>
|
|
||||||
#include <optional>
|
|
||||||
#include <span>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <thread>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <zmq.hpp>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
namespace ipc = cvmmap_streamer::ipc;
|
|
||||||
|
|
||||||
class SharedMemoryRegion {
|
|
||||||
public:
|
|
||||||
static std::expected<SharedMemoryRegion, std::string> create(const std::string &name, std::size_t bytes) {
|
|
||||||
const std::string shm_name = "/" + name;
|
|
||||||
const int fd = shm_open(shm_name.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
|
||||||
if (fd < 0) {
|
|
||||||
return std::unexpected("shm_open failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ftruncate(fd, static_cast<off_t>(bytes)) != 0) {
|
|
||||||
close(fd);
|
|
||||||
shm_unlink(shm_name.c_str());
|
|
||||||
return std::unexpected("ftruncate failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *mapped = static_cast<std::uint8_t *>(mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
|
||||||
if (mapped == MAP_FAILED) {
|
|
||||||
close(fd);
|
|
||||||
shm_unlink(shm_name.c_str());
|
|
||||||
return std::unexpected("mmap failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memset(mapped, 0, bytes);
|
|
||||||
return SharedMemoryRegion(shm_name, fd, mapped, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedMemoryRegion(const SharedMemoryRegion &) = delete;
|
|
||||||
SharedMemoryRegion &operator=(const SharedMemoryRegion &) = delete;
|
|
||||||
|
|
||||||
SharedMemoryRegion(SharedMemoryRegion &&other) noexcept
|
|
||||||
: name_(std::move(other.name_)),
|
|
||||||
fd_(other.fd_),
|
|
||||||
ptr_(other.ptr_),
|
|
||||||
bytes_(other.bytes_) {
|
|
||||||
other.fd_ = -1;
|
|
||||||
other.ptr_ = nullptr;
|
|
||||||
other.bytes_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedMemoryRegion &operator=(SharedMemoryRegion &&other) noexcept {
|
|
||||||
if (this == &other) {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
name_ = std::move(other.name_);
|
|
||||||
fd_ = other.fd_;
|
|
||||||
ptr_ = other.ptr_;
|
|
||||||
bytes_ = other.bytes_;
|
|
||||||
other.fd_ = -1;
|
|
||||||
other.ptr_ = nullptr;
|
|
||||||
other.bytes_ = 0;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
~SharedMemoryRegion() {
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
std::span<std::uint8_t> metadata() {
|
|
||||||
return std::span<std::uint8_t>(ptr_, ipc::kShmPayloadOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
std::span<std::uint8_t> payload(std::size_t payload_bytes) {
|
|
||||||
return std::span<std::uint8_t>(ptr_ + ipc::kShmPayloadOffset, payload_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
SharedMemoryRegion(std::string name, int fd, std::uint8_t *ptr, std::size_t bytes)
|
|
||||||
: name_(std::move(name)), fd_(fd), ptr_(ptr), bytes_(bytes) {}
|
|
||||||
|
|
||||||
void cleanup() {
|
|
||||||
if (ptr_ != nullptr && bytes_ > 0) {
|
|
||||||
munmap(ptr_, bytes_);
|
|
||||||
ptr_ = nullptr;
|
|
||||||
}
|
|
||||||
if (fd_ >= 0) {
|
|
||||||
close(fd_);
|
|
||||||
fd_ = -1;
|
|
||||||
}
|
|
||||||
if (!name_.empty()) {
|
|
||||||
shm_unlink(name_.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string name_;
|
|
||||||
int fd_{-1};
|
|
||||||
std::uint8_t *ptr_{nullptr};
|
|
||||||
std::size_t bytes_{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
void cleanup_zmq_ipc_path(std::string_view endpoint) {
|
|
||||||
constexpr std::string_view kPrefix{"ipc://"};
|
|
||||||
if (!endpoint.starts_with(kPrefix)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto path = endpoint.substr(kPrefix.size());
|
|
||||||
if (!path.empty()) {
|
|
||||||
unlink(std::string(path).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
if (cvmmap_streamer::has_help_flag(argc, argv)) {
|
|
||||||
cvmmap_streamer::sim::print_help();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto config = cvmmap_streamer::sim::parse_runtime_config(argc, argv);
|
|
||||||
if (!config) {
|
|
||||||
spdlog::error("{}", config.error());
|
|
||||||
cvmmap_streamer::sim::print_help();
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto payload_bytes = static_cast<std::size_t>(config->payload_size_bytes());
|
|
||||||
const auto switched_width = config->switch_width.value_or(config->width);
|
|
||||||
const auto switched_height = config->switch_height.value_or(config->height);
|
|
||||||
const auto switched_payload_bytes =
|
|
||||||
static_cast<std::size_t>(switched_width) *
|
|
||||||
static_cast<std::size_t>(switched_height) *
|
|
||||||
static_cast<std::size_t>(config->channels);
|
|
||||||
const auto payload_bytes_max = std::max(payload_bytes, switched_payload_bytes);
|
|
||||||
const auto shm_bytes = ipc::kShmPayloadOffset + payload_bytes_max;
|
|
||||||
|
|
||||||
auto shm = SharedMemoryRegion::create(config->shm_name, shm_bytes);
|
|
||||||
if (!shm) {
|
|
||||||
spdlog::error("failed to create shared memory '{}': {}", config->shm_name, shm.error());
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup_zmq_ipc_path(config->zmq_endpoint);
|
|
||||||
std::optional<zmq::socket_t> publisher;
|
|
||||||
try {
|
|
||||||
static zmq::context_t context{1};
|
|
||||||
publisher.emplace(context, zmq::socket_type::pub);
|
|
||||||
publisher->bind(config->zmq_endpoint);
|
|
||||||
} catch (const zmq::error_t &e) {
|
|
||||||
spdlog::error("failed to bind zmq endpoint '{}': {}", config->zmq_endpoint, e.what());
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
ipc::FrameInfo frame_info{
|
|
||||||
.width = config->width,
|
|
||||||
.height = config->height,
|
|
||||||
.channels = config->channels,
|
|
||||||
.depth = config->depth,
|
|
||||||
.pixel_format = config->pixel_format,
|
|
||||||
.buffer_size = config->payload_size_bytes()};
|
|
||||||
|
|
||||||
std::array<std::uint8_t, cvmmap_streamer::sim::kSyncMessageBytes> sync_msg{};
|
|
||||||
std::array<std::uint8_t, cvmmap_streamer::sim::kModuleStatusMessageBytes> status_msg{};
|
|
||||||
|
|
||||||
const auto send_status = [&](ipc::ModuleStatus status, std::uint32_t frame_count) {
|
|
||||||
cvmmap_streamer::sim::write_module_status_message(status_msg, config->label, status);
|
|
||||||
publisher->send(zmq::buffer(status_msg), zmq::send_flags::none);
|
|
||||||
spdlog::info("status={} frame_count={}", static_cast<std::int32_t>(status), frame_count);
|
|
||||||
};
|
|
||||||
|
|
||||||
std::uint64_t timestamp_ns = 1'000'000'000ull;
|
|
||||||
const std::uint64_t tick_ns =
|
|
||||||
(config->fps == 0)
|
|
||||||
? 0ull
|
|
||||||
: std::max<std::uint64_t>(1ull, 1'000'000'000ull / static_cast<std::uint64_t>(config->fps));
|
|
||||||
|
|
||||||
spdlog::info(
|
|
||||||
"sim start shm='{}' zmq='{}' label='{}' frames={} fps={} {}x{} payload={}",
|
|
||||||
config->shm_name,
|
|
||||||
config->zmq_endpoint,
|
|
||||||
config->label,
|
|
||||||
config->frames,
|
|
||||||
config->fps,
|
|
||||||
config->width,
|
|
||||||
config->height,
|
|
||||||
payload_bytes);
|
|
||||||
|
|
||||||
send_status(ipc::ModuleStatus::Online, 0);
|
|
||||||
|
|
||||||
bool reset_sent{false};
|
|
||||||
std::uint32_t reset_every_count{0};
|
|
||||||
bool format_switched{false};
|
|
||||||
for (std::uint32_t frame_count = 1; frame_count <= config->frames; ++frame_count) {
|
|
||||||
if (!format_switched && config->switch_format_at && *config->switch_format_at == frame_count) {
|
|
||||||
frame_info.width = switched_width;
|
|
||||||
frame_info.height = switched_height;
|
|
||||||
frame_info.buffer_size = static_cast<std::uint32_t>(switched_payload_bytes);
|
|
||||||
format_switched = true;
|
|
||||||
spdlog::info(
|
|
||||||
"sim format switch at frame={} new={}x{} channels={} payload={}",
|
|
||||||
frame_count,
|
|
||||||
frame_info.width,
|
|
||||||
frame_info.height,
|
|
||||||
static_cast<unsigned>(frame_info.channels),
|
|
||||||
frame_info.buffer_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto active_payload_bytes = static_cast<std::size_t>(frame_info.buffer_size);
|
|
||||||
auto payload = shm->payload(active_payload_bytes);
|
|
||||||
cvmmap_streamer::sim::write_deterministic_payload(
|
|
||||||
payload,
|
|
||||||
frame_count,
|
|
||||||
frame_info.width,
|
|
||||||
frame_info.height,
|
|
||||||
config->channels);
|
|
||||||
|
|
||||||
cvmmap_streamer::sim::write_frame_metadata(
|
|
||||||
shm->metadata(),
|
|
||||||
frame_info,
|
|
||||||
frame_count,
|
|
||||||
timestamp_ns);
|
|
||||||
|
|
||||||
cvmmap_streamer::sim::write_sync_message(
|
|
||||||
sync_msg,
|
|
||||||
config->label,
|
|
||||||
frame_count,
|
|
||||||
timestamp_ns);
|
|
||||||
publisher->send(zmq::buffer(sync_msg), zmq::send_flags::none);
|
|
||||||
|
|
||||||
spdlog::info("sync frame_count={} timestamp_ns={}", frame_count, timestamp_ns);
|
|
||||||
|
|
||||||
if (!reset_sent && config->emit_reset_at && *config->emit_reset_at == frame_count) {
|
|
||||||
send_status(ipc::ModuleStatus::StreamReset, frame_count);
|
|
||||||
reset_sent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config->emit_reset_every && *config->emit_reset_every > 0 && (frame_count % *config->emit_reset_every) == 0) {
|
|
||||||
send_status(ipc::ModuleStatus::StreamReset, frame_count);
|
|
||||||
reset_sent = true;
|
|
||||||
reset_every_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp_ns += tick_ns;
|
|
||||||
if (config->fps > 0) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::nanoseconds(tick_ns));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
send_status(ipc::ModuleStatus::Offline, config->frames);
|
|
||||||
cleanup_zmq_ipc_path(config->zmq_endpoint);
|
|
||||||
spdlog::info(
|
|
||||||
"sim complete frames={} reset_sent={} periodic_resets={} format_switched={}",
|
|
||||||
config->frames,
|
|
||||||
reset_sent ? "true" : "false",
|
|
||||||
reset_every_count,
|
|
||||||
format_switched ? "true" : "false");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace cvmmap_streamer {
|
|
||||||
|
|
||||||
void protocol_step() {}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,411 +0,0 @@
|
|||||||
#include "cvmmap_streamer/sim/options.hpp"
|
|
||||||
|
|
||||||
#include <CLI/CLI.hpp>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <charconv>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <limits>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
|
|
||||||
namespace cvmmap_streamer::sim {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::string trim_copy(std::string value) {
|
|
||||||
auto not_space = [](unsigned char c) {
|
|
||||||
return c != ' ' && c != '\t' && c != '\n' && c != '\r';
|
|
||||||
};
|
|
||||||
while (!value.empty() &&
|
|
||||||
!not_space(static_cast<unsigned char>(value.front()))) {
|
|
||||||
value.erase(value.begin());
|
|
||||||
}
|
|
||||||
while (!value.empty() &&
|
|
||||||
!not_space(static_cast<unsigned char>(value.back()))) {
|
|
||||||
value.pop_back();
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
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, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
unsigned long parsed{0};
|
|
||||||
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) + "'");
|
|
||||||
}
|
|
||||||
if (parsed > std::numeric_limits<std::uint32_t>::max()) {
|
|
||||||
return std::unexpected("value out of range for " + std::string(flag_name) +
|
|
||||||
": '" + std::string(raw) + "'");
|
|
||||||
}
|
|
||||||
return static_cast<std::uint32_t>(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::expected<std::uint16_t, std::string>
|
|
||||||
parse_u16(std::string_view raw, std::string_view flag_name) {
|
|
||||||
unsigned int parsed{0};
|
|
||||||
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) + "'");
|
|
||||||
}
|
|
||||||
if (parsed > std::numeric_limits<std::uint16_t>::max()) {
|
|
||||||
return std::unexpected("value out of range for " + std::string(flag_name) +
|
|
||||||
": '" + std::string(raw) + "'");
|
|
||||||
}
|
|
||||||
return static_cast<std::uint16_t>(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::expected<void, std::string>
|
|
||||||
validate_runtime_config(const RuntimeConfig &config) {
|
|
||||||
if (config.frames == 0) {
|
|
||||||
return std::unexpected("--frames must be > 0");
|
|
||||||
}
|
|
||||||
if (config.width == 0 || config.height == 0) {
|
|
||||||
return std::unexpected("--width and --height must be > 0");
|
|
||||||
}
|
|
||||||
if (config.label.empty()) {
|
|
||||||
return std::unexpected("--label must not be empty");
|
|
||||||
}
|
|
||||||
if (config.label.size() > ipc::kLabelLenMax) {
|
|
||||||
return std::unexpected("--label exceeds 24 bytes");
|
|
||||||
}
|
|
||||||
if (config.shm_name.empty()) {
|
|
||||||
return std::unexpected("--shm-name must not be empty");
|
|
||||||
}
|
|
||||||
if (config.zmq_endpoint.empty()) {
|
|
||||||
return std::unexpected("--zmq-endpoint must not be empty");
|
|
||||||
}
|
|
||||||
if (config.emit_reset_at && *config.emit_reset_at == 0) {
|
|
||||||
return std::unexpected("--emit-reset-at must be in [1, --frames]");
|
|
||||||
}
|
|
||||||
if (config.emit_reset_at && *config.emit_reset_at > config.frames) {
|
|
||||||
return std::unexpected("--emit-reset-at must be in [1, --frames]");
|
|
||||||
}
|
|
||||||
if (config.emit_reset_every && *config.emit_reset_every == 0) {
|
|
||||||
return std::unexpected("--emit-reset-every must be > 0");
|
|
||||||
}
|
|
||||||
if (config.switch_format_at && *config.switch_format_at == 0) {
|
|
||||||
return std::unexpected("--switch-format-at must be in [1, --frames]");
|
|
||||||
}
|
|
||||||
if (config.switch_format_at && *config.switch_format_at > config.frames) {
|
|
||||||
return std::unexpected("--switch-format-at must be in [1, --frames]");
|
|
||||||
}
|
|
||||||
if (config.switch_width && *config.switch_width == 0) {
|
|
||||||
return std::unexpected("--switch-width must be > 0");
|
|
||||||
}
|
|
||||||
if (config.switch_height && *config.switch_height == 0) {
|
|
||||||
return std::unexpected("--switch-height must be > 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto payload_size = static_cast<std::uint64_t>(config.width) *
|
|
||||||
static_cast<std::uint64_t>(config.height) *
|
|
||||||
static_cast<std::uint64_t>(config.channels);
|
|
||||||
if (payload_size >
|
|
||||||
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_height = config.switch_height.value_or(config.height);
|
|
||||||
const auto switched_payload_size =
|
|
||||||
static_cast<std::uint64_t>(switched_width) *
|
|
||||||
static_cast<std::uint64_t>(switched_height) *
|
|
||||||
static_cast<std::uint64_t>(config.channels);
|
|
||||||
if (switched_payload_size >
|
|
||||||
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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
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