Program Listing for File cli_main.cpp

Program Listing for File cli_main.cpp#

↰ Return to documentation for file (Src/cli_main.cpp)

#include <atomic>
#include <chrono>
#include <csignal>
#include <iostream>
#include <string>
#include <string_view>
#include <thread>

#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/flags/usage.h"

import kataglyphis.inference;
import kataglyphis.webrtc_streamer;
import kataglyphis.config_loader;
import kataglyphis.project_config;

ABSL_FLAG(bool, webrtc, false, "Start WebRTC streaming");
ABSL_FLAG(std::string, config, "", "Load settings from JSON config file");
ABSL_FLAG(std::string, server, "", "Signalling server URI (overrides config file default)");
ABSL_FLAG(std::string, source, "libcamera", "Video source: libcamera, v4l2, test, file, uri");
ABSL_FLAG(std::string, device, "/dev/video0", "V4L2 device path");
ABSL_FLAG(std::string, camera_id, "", "Libcamera camera name");
ABSL_FLAG(std::string, input_path, "", "Local media file path for --source file");
ABSL_FLAG(std::string, input_uri, "", "Media URI for --source uri");
ABSL_FLAG(std::uint32_t, width, 0, "Video width in pixels (0 = use config default)");
ABSL_FLAG(std::uint32_t, height, 0, "Video height in pixels (0 = use config default)");
ABSL_FLAG(std::uint32_t, fps, 0, "Framerate (0 = use config default)");
ABSL_FLAG(std::string, encoder, "h264-hw", "Encoder: h264-hw, h264-sw, vp8, vp9");
ABSL_FLAG(std::uint32_t, bitrate, 0, "Bitrate in kbps (0 = use config default)");
ABSL_FLAG(bool,
  gst_check,
  false,
  "Check that GStreamer finds every element the pipelines use, in a package only its own");

namespace {
std::atomic<bool> g_running{ true };

struct CliOverrideFlags
{
    bool server{ false };
    bool source{ false };
    bool device{ false };
    bool camera_id{ false };
    bool input_path{ false };
    bool input_uri{ false };
    bool width{ false };
    bool height{ false };
    bool fps{ false };
    bool encoder{ false };
    bool bitrate{ false };
};

void signal_handler(int /*signal*/) { g_running.store(false); }

// A missing required element, or one a package loads from outside its own plugins, fails the check.
auto check_gstreamer(int *argc, char ***argv) -> int
{
    if (!kataglyphis::webrtc::WebRTCStreamer::initialize(argc, argv)) {
        std::cerr << "GStreamer did not initialize\n";
        return 1;
    }
    const std::string bundled = kataglyphis::webrtc::WebRTCStreamer::bundled_plugin_dir();
    std::cout << "plugins: " << (bundled.empty() ? std::string("not a package, GStreamer's own search path") : bundled)
              << '\n';
    int failures = 0;
    for (const auto &location : kataglyphis::webrtc::WebRTCStreamer::locate_elements()) {
        const bool optional = location.element == "libcamerasrc" || location.element == "openh264enc";
        const bool outside = location.found && !bundled.empty() && !location.plugin_file.starts_with(bundled + "/");
        const char *verdict = location.found ? (outside ? "OUTSIDE" : "ok") : (optional ? "absent" : "MISSING");
        std::cout << verdict << ' ' << location.element << ' ' << location.plugin_file << '\n';
        if ((!location.found && !optional) || outside) { ++failures; }
    }
    return failures == 0 ? 0 : 1;
}


auto was_flag_provided(std::string_view argument, std::string_view flag_name) -> bool
{
    const std::string long_flag = "--" + std::string(flag_name);
    const std::string short_flag = "-" + std::string(flag_name);
    return argument == long_flag || argument.starts_with(long_flag + "=") || argument == short_flag
           || argument.starts_with(short_flag + "=");
}

auto detect_cli_override_flags(int argc, char **argv) -> CliOverrideFlags
{
    CliOverrideFlags flags;

    for (int index = 1; index < argc; ++index) {
        const std::string_view argument = argv[index];
        flags.server = flags.server || was_flag_provided(argument, "server");
        flags.source = flags.source || was_flag_provided(argument, "source");
        flags.device = flags.device || was_flag_provided(argument, "device");
        flags.camera_id =
          flags.camera_id || was_flag_provided(argument, "camera_id") || was_flag_provided(argument, "camera-id");
        flags.input_path =
          flags.input_path || was_flag_provided(argument, "input_path") || was_flag_provided(argument, "input-path");
        flags.input_uri =
          flags.input_uri || was_flag_provided(argument, "input_uri") || was_flag_provided(argument, "input-uri");
        flags.width = flags.width || was_flag_provided(argument, "width");
        flags.height = flags.height || was_flag_provided(argument, "height");
        flags.fps = flags.fps || was_flag_provided(argument, "fps");
        flags.encoder = flags.encoder || was_flag_provided(argument, "encoder");
        flags.bitrate = flags.bitrate || was_flag_provided(argument, "bitrate");
    }

    return flags;
}

auto parse_video_source(std::string_view name) -> kataglyphis::webrtc::VideoSource
{
    if (name == "v4l2") { return kataglyphis::webrtc::VideoSource::V4L2; }
    if (name == "test") { return kataglyphis::webrtc::VideoSource::TestPattern; }
    if (name == "file") { return kataglyphis::webrtc::VideoSource::File; }
    if (name == "uri") { return kataglyphis::webrtc::VideoSource::Uri; }
    if (name != "libcamera") { std::cerr << "Warning: unknown source '" << name << "', falling back to libcamera\n"; }
    return kataglyphis::webrtc::VideoSource::Libcamera;
}

auto parse_video_encoder(std::string_view name) -> kataglyphis::webrtc::VideoEncoder
{
    if (name == "h264-sw") { return kataglyphis::webrtc::VideoEncoder::H264_Software; }
    if (name == "vp8") { return kataglyphis::webrtc::VideoEncoder::VP8; }
    if (name == "vp9") { return kataglyphis::webrtc::VideoEncoder::VP9; }
    if (name != "h264-hw") { std::cerr << "Warning: unknown encoder '" << name << "', falling back to h264-hw\n"; }
    return kataglyphis::webrtc::VideoEncoder::H264_Hardware;
}

auto state_to_string(kataglyphis::webrtc::StreamState state) -> const char *
{
    using kataglyphis::webrtc::StreamState;
    switch (state) {
    case StreamState::Idle:
        return "Idle";
    case StreamState::Connecting:
        return "Connecting";
    case StreamState::Negotiating:
        return "Negotiating";
    case StreamState::Streaming:
        return "Streaming";
    case StreamState::Paused:
        return "Paused";
    case StreamState::Error:
        return "Error";
    case StreamState::Disconnected:
        return "Disconnected";
    }
    return "Unknown";
}

auto apply_cli_overrides(kataglyphis::webrtc::StreamConfig &config, const CliOverrideFlags &override_flags) -> void
{
    if (override_flags.server) {
        const auto cli_server = absl::GetFlag(FLAGS_server);
        config.signalling_server_uri = cli_server;
    }
    if (override_flags.device) { config.v4l2_device = absl::GetFlag(FLAGS_device); }
    if (override_flags.camera_id) { config.camera_id = absl::GetFlag(FLAGS_camera_id); }
    if (override_flags.input_path) { config.input_path = absl::GetFlag(FLAGS_input_path); }
    if (override_flags.input_uri) { config.input_uri = absl::GetFlag(FLAGS_input_uri); }
    if (override_flags.width) { config.width = absl::GetFlag(FLAGS_width); }
    if (override_flags.height) { config.height = absl::GetFlag(FLAGS_height); }
    if (override_flags.fps) { config.framerate = absl::GetFlag(FLAGS_fps); }
    if (override_flags.bitrate) {
        const auto cli_bitrate = absl::GetFlag(FLAGS_bitrate);
        config.bitrate_kbps = cli_bitrate;
    }
}
}// namespace

auto main(int argc, char **argv) -> int
{
    const auto override_flags = detect_cli_override_flags(argc, argv);

    absl::SetProgramUsageMessage(
      "AccelerANTgine — WebRTC streaming and inference engine\n\n"
      "Examples:\n"
      "  --webrtc --config /path/to/webrtc_settings.json\n"
      "  --webrtc --server ws://192.168.1.100:8443\n"
      "  --webrtc --source v4l2 --device /dev/video0\n"
      "  --webrtc --source file --input_path /path/to/video.mp4\n"
      "  --webrtc --source uri --input_uri rtsp://camera.example/stream\n"
      "  --webrtc --source test   # For testing without camera");

    absl::ParseCommandLine(argc, argv);

    if (absl::GetFlag(FLAGS_gst_check)) { return check_gstreamer(&argc, &argv); }

    const bool start_webrtc = absl::GetFlag(FLAGS_webrtc);
    if (!start_webrtc) {
        std::cout << "AccelerANTgine " << kataglyphis::project_config::project_version_major << "."
                  << kataglyphis::project_config::project_version_minor << '\n';
        std::cout << "Use --help for usage information\n";
        return 0;
    }

    const auto source = parse_video_source(absl::GetFlag(FLAGS_source));
    const auto encoder = parse_video_encoder(absl::GetFlag(FLAGS_encoder));
    const auto source_override_mode = override_flags.source ? kataglyphis::webrtc::ConfigOverrideMode::Override
                                                            : kataglyphis::webrtc::ConfigOverrideMode::UseConfig;
    const auto encoder_override_mode = override_flags.encoder ? kataglyphis::webrtc::ConfigOverrideMode::Override
                                                              : kataglyphis::webrtc::ConfigOverrideMode::UseConfig;
    auto webrtc_config = kataglyphis::config::get_default_webrtc_config();

    const std::string config_file_path = absl::GetFlag(FLAGS_config);

    if (!config_file_path.empty()) {
        std::cout << "Loading configuration from: " << config_file_path << '\n';
        auto result = kataglyphis::config::load_webrtc_config(config_file_path);
        if (result) {
            webrtc_config = *result;
            std::cout << "Configuration loaded successfully\n";
        } else {
            std::cerr << "Warning: Failed to load config file, using defaults\n";
        }
    }

    auto config = kataglyphis::webrtc::create_stream_config_from_webrtc_config(
      webrtc_config, source, encoder, source_override_mode, encoder_override_mode);

    apply_cli_overrides(config, override_flags);

    std::cout << "Initializing WebRTC streaming...\n";

    auto init_result = kataglyphis::webrtc::WebRTCStreamer::initialize(&argc, &argv);
    if (!init_result) {
        std::cerr << "Failed to initialize GStreamer for WebRTC\n";
        return 1;
    }

    kataglyphis::webrtc::WebRTCStreamer streamer;

    streamer.set_state_callback(
      [](kataglyphis::webrtc::StreamState old_state, kataglyphis::webrtc::StreamState new_state) -> void {
          std::cout << "State: " << state_to_string(old_state) << " -> " << state_to_string(new_state) << '\n';
      });

    streamer.set_error_callback([](kataglyphis::webrtc::WebRTCError /*error*/, const std::string &message) -> void {
        std::cerr << "Error: " << message << '\n';
    });

    auto configure_result = streamer.configure(config);
    if (!configure_result) {
        std::cerr << "Failed to configure WebRTC streamer\n";
        return 1;
    }

    std::cout << "Connecting to signalling server: " << config.signalling_server_uri << '\n';
    std::cout << "Producer ID: " << streamer.get_producer_id() << '\n';
    std::cout << "Resolution: " << config.width << "x" << config.height << "@" << config.framerate << "fps\n";

    auto start_result = streamer.start();
    if (!start_result) {
        std::cerr << "Failed to start WebRTC stream\n";
        return 1;
    }

    (void)std::signal(SIGINT, signal_handler);
    (void)std::signal(SIGTERM, signal_handler);

    std::cout << "Streaming... Press Ctrl+C to stop.\n";

    while (g_running.load()) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));

        auto state = streamer.get_state();
        if (state == kataglyphis::webrtc::StreamState::Error
            || state == kataglyphis::webrtc::StreamState::Disconnected) {
            std::cerr << "Stream ended unexpectedly\n";
            break;
        }
    }

    std::cout << "\nStopping stream...\n";
    (void)streamer.stop();

    kataglyphis::webrtc::WebRTCStreamer::deinitialize();

    std::cout << "Done.\n";
    return 0;
}