diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..52f58b2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,68 @@ +{ + "files.associations": { + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "filesystem": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ratio": "cpp", + "source_location": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "shared_mutex": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/config.json b/config.json index 887901e..be2e8d7 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,14 @@ { - "srs_server": "rtmp://192.168.4.12/live", + "mqtt_server": { + "veh_id": 20004, + "address": "192.168.3.19", + "port": 1883, + "need_username_pwd": true, + "client_id": "20004_vehmedia", + "username": "20004_4A:69:BE:32:59:AE", + "password": "31d6bb29f177d5bf8560756c0f0e63c63fd412e52c5b9ea59476024eab893884a5f34f0637e0fe3ad42b802c16edb6feb37cde613957c3540c060c07b230cb0aa6b4547bb86fcae43d484179d3a11a1969a2f367ec0ceede4c10510757a89927af4c2d0c0484476be3241a9ff9242e7401f3fbcd824b5cfb19674663b7045e32dd2f97b4", + "mqtt_heart_threshold": 2000 + }, "cameras": [ { "device": "/dev/video11", @@ -74,4 +83,4 @@ "fps": 30 } ] -} +} \ No newline at end of file diff --git a/include/app_config.hpp b/include/app_config.hpp new file mode 100644 index 0000000..679f0a7 --- /dev/null +++ b/include/app_config.hpp @@ -0,0 +1,160 @@ +// app_config.hpp +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "logger.hpp" + +using json = nlohmann::json; +using ordered_json = nlohmann::ordered_json; + +// ------------------- 摄像头结构体 ------------------- +struct Camera +{ + std::string device; + std::string name; + int width, height, fps; + bool enabled; +}; + +// ------------------- MQTT Topic ------------------- +struct VehicleMQTTTopics +{ + std::string heartbeat_up; + std::string video_down; + std::string video_down_ack; + std::string substream_down; + std::string substream_down_ack; + std::string reset_down; + std::string reset_down_ack; + + void fill_with_veh_id(const std::string &vehId) + { + heartbeat_up = "adcpcmcc/v1/vehmedia/" + vehId + "/heartbeat/up"; + video_down = "adcpcmcc/v1/vehmedia/" + vehId + "/video/down"; + video_down_ack = "adcpcmcc/v1/vehmedia/" + vehId + "/video/down/ack"; + substream_down = "adcpcmcc/v1/vehmedia/" + vehId + "/substream/down"; + substream_down_ack = "adcpcmcc/v1/vehmedia/" + vehId + "/substream/down/ack"; + reset_down = "adcpcmcc/v1/vehmedia/" + vehId + "/reset/down"; + reset_down_ack = "adcpcmcc/v1/vehmedia/" + vehId + "/reset/down/ack"; + } +}; + +// ------------------- MQTT 配置 ------------------- +struct MQTTConfig +{ + std::string vehicle_id; + std::string server_ip; + int server_port; + bool need_username_pwd; + std::string client_id; + std::string username; + std::string password; + int keep_alive; + VehicleMQTTTopics topics; +}; + +// ------------------- 总配置 ------------------- +struct AppConfig +{ + std::vector cameras; + MQTTConfig mqtt; + + static AppConfig load_from_file(const std::string &filepath) + { + AppConfig cfg; + + std::ifstream ifs(filepath); + if (!ifs.is_open()) + { + LOG_ERROR("[Config] Failed to open config file: " + filepath); + throw std::runtime_error("Failed to open config file: " + filepath); + } + + json j; + ifs >> j; + + // 读取摄像头 + if (j.contains("cameras")) + { + for (auto &c : j["cameras"]) + { + Camera cam; + cam.device = c.value("device", ""); + cam.name = c.value("name", ""); + cam.width = c.value("width", 1280); + cam.height = c.value("height", 720); + cam.fps = c.value("fps", 30); + cam.enabled = c.value("enabled", false); + cfg.cameras.push_back(cam); + LOG_INFO("[Config] Loaded camera: " + cam.name + + " (" + cam.device + "), enabled=" + std::to_string(cam.enabled)); + } + } + + // 读取 MQTT + if (!j.contains("mqtt_server")) + { + LOG_ERROR("[Config] Missing 'mqtt_server' section"); + throw std::runtime_error("Config file missing 'mqtt_server'"); + } + + auto &m = j["mqtt_server"]; + cfg.mqtt.vehicle_id = std::to_string(m.value("veh_id", 0)); + cfg.mqtt.server_ip = m.value("address", ""); + cfg.mqtt.server_port = m.value("port", 1883); + cfg.mqtt.need_username_pwd = m.value("need_username_pwd", true); + cfg.mqtt.client_id = m.value("client_id", ""); + cfg.mqtt.username = m.value("username", ""); + cfg.mqtt.password = m.value("password", ""); + cfg.mqtt.keep_alive = m.value("mqtt_heart_threshold", 2000); + cfg.mqtt.topics.fill_with_veh_id(cfg.mqtt.vehicle_id); + + LOG_INFO("[Config] Loaded MQTT server: " + cfg.mqtt.server_ip + + ":" + std::to_string(cfg.mqtt.server_port)); + LOG_INFO("[Config] MQTT client ID: " + cfg.mqtt.client_id); + LOG_INFO("[Config] MQTT Topics: " + cfg.mqtt.topics.heartbeat_up + ", " + + cfg.mqtt.topics.video_down + ", " + cfg.mqtt.topics.video_down_ack); + + return cfg; + } +}; + +// ------------------- 辅助函数 ------------------- +inline std::string get_executable_dir() +{ + char result[PATH_MAX] = {0}; + ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); + if (count == -1) + { + LOG_ERROR("[Config] Failed to read /proc/self/exe"); + throw std::runtime_error("Failed to read /proc/self/exe"); + } + + std::string full_path(result, count); + auto pos = full_path.find_last_of('/'); + if (pos == std::string::npos) + { + LOG_ERROR("[Config] Failed to find executable directory"); + throw std::runtime_error("Failed to find executable directory"); + } + + return full_path.substr(0, pos); +} + +inline std::string get_executable_dir_file_path(const std::string &filename) +{ + std::string dir = get_executable_dir(); + if (dir.back() != '/') + dir += '/'; + return dir + filename; +} + +// 全局配置变量 +extern AppConfig g_app_config; diff --git a/include/logger.hpp b/include/logger.hpp index 4bb2b48..d4e34ee 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -1,3 +1,4 @@ +// logger.hpp #pragma once #include diff --git a/include/mqtt_config.hpp b/include/mqtt_config.hpp deleted file mode 100644 index 7a8167f..0000000 --- a/include/mqtt_config.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include "logger.hpp" - -using ordered_json = nlohmann::ordered_json; - -struct MQTTTopics -{ - std::string uplink_1; - std::string uplink_2; - std::string downlink; -}; - -struct MQTTPorts -{ - int config_port; - int uplink_1_port; - int uplink_2_port; - int downlink_port; -}; - -struct MQTTConfig -{ - std::string device_id; - std::string vin; - std::string server_ip; - int server_port; - std::string client_id; - std::string username; - std::string password; - int qos; - int keep_alive; - bool clean_session; - MQTTTopics topics; - MQTTPorts ports; - - static MQTTConfig load_from_file(const std::string &filepath); - // 用完整 JSON 对象更新原始配置中已有字段 - bool update_mqtt_config(const ordered_json &new_values, - const std::string &filepath = "config.json"); -}; - -std::string get_executable_dir(); -std::string get_executable_dir_file_path(const std::string &filename); - -// 全局配置变量 -extern MQTTConfig g_mqtt_config; diff --git a/include/rtsp_manager.hpp b/include/rtsp_manager.hpp new file mode 100644 index 0000000..a548726 --- /dev/null +++ b/include/rtsp_manager.hpp @@ -0,0 +1,21 @@ +// rtsp_manager.hpp +#pragma once + +#include +#include +#include "app_config.hpp" + +// RTSP 管理器,负责启动/关闭 RTSP 服务器 +class RTSPManager +{ +public: + static void init(); + static void start(const std::vector &cameras); + static void stop(); + +private: + static GMainLoop *loop; + static GstRTSPServer *server; + + static GstRTSPMediaFactory *create_media_factory(const Camera &cam); +}; diff --git a/src/app_config.cpp b/src/app_config.cpp new file mode 100644 index 0000000..2037f52 --- /dev/null +++ b/src/app_config.cpp @@ -0,0 +1,5 @@ +// app_config.cpp +#include "app_config.hpp" + +// 定义全局配置变量 +AppConfig g_app_config; diff --git a/src/logger.cpp b/src/logger.cpp index acecf7c..2d53b37 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -1,3 +1,4 @@ +// logger.cpp #include "logger.hpp" #include #include diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..fcf8c8d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,38 @@ +// main.cpp +#include "app_config.hpp" +#include "rtsp_manager.hpp" +#include "logger.hpp" +#include +#include + +std::atomic g_running(true); + +void signalHandler(int signum) +{ + g_running = false; + RTSPManager::stop(); +} + +int main() +{ + signal(SIGINT, signalHandler); + signal(SIGPIPE, SIG_IGN); + + // 初始化日志文件 + Logger::set_log_to_file("app.log"); + + try + { + g_app_config = AppConfig::load_from_file("config.json"); + } + catch (const std::exception &e) + { + LOG_ERROR(std::string("Failed to load config: ") + e.what()); + return -1; + } + + RTSPManager::init(); + RTSPManager::start(g_app_config.cameras); + + return 0; +} \ No newline at end of file diff --git a/src/mqtt_config.cpp b/src/mqtt_config.cpp deleted file mode 100644 index 6482170..0000000 --- a/src/mqtt_config.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "mqtt_config.hpp" - -MQTTConfig g_mqtt_config; - -MQTTConfig MQTTConfig::load_from_file(const std::string &filepath) -{ - MQTTConfig cfg; - - std::ifstream ifs(filepath); - if (!ifs) - { - LOG_ERROR("[Config] Failed to open config file: " + filepath); - throw std::runtime_error("Failed to open config file: " + filepath); - } - - nlohmann::json j; - ifs >> j; - - cfg.device_id = j["device_id"]; - cfg.vin = j["vin"]; - cfg.server_ip = j["server_ip"]; - cfg.server_port = j["server_port"]; - cfg.client_id = j["client_id"]; - cfg.username = j["username"]; - cfg.password = j["password"]; - cfg.qos = j["qos"]; - cfg.keep_alive = j["keep_alive"]; - cfg.clean_session = j["clean_session"]; - - const std::string &id = cfg.device_id; - cfg.topics.uplink_1 = j["topics"]["uplink_1"].get() + id; - cfg.topics.uplink_2 = j["topics"]["uplink_2"].get() + id; - cfg.topics.downlink = j["topics"]["downlink"].get() + id; - - cfg.ports.config_port = j["ports"]["config_port"]; - cfg.ports.uplink_1_port = j["ports"]["uplink_1_port"]; - cfg.ports.uplink_2_port = j["ports"]["uplink_2_port"]; - cfg.ports.downlink_port = j["ports"]["downlink_port"]; - - LOG_INFO("[Config] Loaded config from " + filepath); - LOG_INFO(" - MQTT Server: " + cfg.server_ip + ":" + std::to_string(cfg.server_port)); - LOG_INFO(" - Client ID: " + cfg.client_id); - LOG_INFO(" - Topics: " + cfg.topics.uplink_1 + ", " + cfg.topics.uplink_2 + ", " + cfg.topics.downlink); - - return cfg; -} - -bool MQTTConfig::update_mqtt_config(const ordered_json &new_values, - const std::string &filepath) -{ - std::ifstream ifs(filepath); - if (!ifs.is_open()) - { - LOG_ERROR("[ConfigEdit] Failed to open config file: " + filepath); - return false; - } - - ordered_json j; - try - { - ifs >> j; - ifs.close(); - } - catch (const std::exception &e) - { - LOG_ERROR(std::string("[ConfigEdit] JSON parse error: ") + e.what()); - return false; - } - - for (auto it = new_values.begin(); it != new_values.end(); ++it) - { - const std::string &key = it.key(); - const auto &val = it.value(); - - if (j.contains(key)) - { - LOG_INFO("[ConfigEdit] Updating key: " + key + " -> " + val.dump()); - j[key] = val; - } - else - { - LOG_WARN("[ConfigEdit] Warning: key '" + key + "' not found in config, ignored."); - } - } - - std::ofstream ofs(filepath); - if (!ofs.is_open()) - { - LOG_ERROR("[ConfigEdit] Failed to write updated config file: " + filepath); - return false; - } - - ofs << j.dump(4); - ofs.close(); - return true; -} - -std::string get_executable_dir() -{ - char result[PATH_MAX] = {0}; - ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); - if (count == -1) - { - throw std::runtime_error("Failed to read /proc/self/exe"); - } - - std::string full_path(result, count); - auto pos = full_path.find_last_of('/'); - if (pos == std::string::npos) - { - throw std::runtime_error("Failed to find executable directory"); - } - return full_path.substr(0, pos); -} - -std::string get_executable_dir_file_path(const std::string &filename) -{ - std::string dir = get_executable_dir(); - if (dir.back() != '/') - dir += '/'; - return dir + filename; -} \ No newline at end of file diff --git a/src/rtsp.cpp b/src/rtsp.cpp deleted file mode 100644 index 2992c9a..0000000 --- a/src/rtsp.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// camera_to_rtsp.cpp -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using json = nlohmann::json; - -GMainLoop *loop = nullptr; - -// 全局退出标志 -std::atomic g_running(true); - -// Ctrl+C 信号处理 -void signalHandler(int signum) -{ - g_running = false; - if (loop) - g_main_loop_quit(loop); -} - -// GStreamer 日志处理 -void gstreamer_log_handler(const gchar *domain, GLogLevelFlags level, - const gchar *message, gpointer) -{ - if (level & G_LOG_LEVEL_ERROR) - std::cerr << "[GStreamer-ERROR] " << (domain ? domain : "unknown") << ": " << message << std::endl; -} - -// 初始化 GStreamer -void init_gstreamer() -{ - gst_init(nullptr, nullptr); - g_log_set_default_handler(gstreamer_log_handler, nullptr); - // 设置GStreamer调试级别为警告及以上 - setenv("GST_DEBUG", "*:2", 1); -} - -// 摄像头结构体 -struct Camera -{ - std::string device; - std::string name; - int width, height, fps; - bool enabled; - GstElement *src = nullptr; - GstRTSPMediaFactory *factory = nullptr; -}; - -// 创建RTSP媒体工厂 -GstRTSPMediaFactory *create_media_factory(const Camera &cam) -{ - std::string launch_str = - "( v4l2src device=" + cam.device + - " ! video/x-raw,format=NV12,width=" + std::to_string(cam.width) + - ",height=" + std::to_string(cam.height) + - ",framerate=" + std::to_string(cam.fps) + "/1" - " ! videoconvert ! queue ! mpph264enc ! rtph264pay name=pay0 pt=96 )"; - - GstRTSPMediaFactory *factory = gst_rtsp_media_factory_new(); - gst_rtsp_media_factory_set_launch(factory, launch_str.c_str()); - gst_rtsp_media_factory_set_shared(factory, TRUE); - - return factory; -} - -// 主函数 -int main() -{ - signal(SIGINT, signalHandler); - signal(SIGPIPE, SIG_IGN); - init_gstreamer(); - - // 读取配置文件 - std::ifstream ifs("config.json"); - if (!ifs.is_open()) - { - std::cerr << "Failed to open config.json" << std::endl; - return -1; - } - - json j; - ifs >> j; - - std::vector cameras; - for (auto &c : j["cameras"]) - { - Camera cam; - cam.device = c["device"]; - cam.name = c["name"]; - cam.width = c["width"]; - cam.height = c["height"]; - cam.fps = c["fps"]; - cam.enabled = c["enabled"]; - - if (cam.enabled) - { - cameras.push_back(cam); - } - } - - // 创建RTSP服务器 - GstRTSPServer *server = gst_rtsp_server_new(); - gst_rtsp_server_set_service(server, "8554"); // 设置RTSP服务器端口 - - // 获取服务器挂载点 - GstRTSPMountPoints *mounts = gst_rtsp_server_get_mount_points(server); - - // 为每个摄像头创建媒体工厂并添加到挂载点 - for (auto &cam : cameras) - { - if (!cam.enabled) - continue; - - cam.factory = create_media_factory(cam); - std::string mount_point = "/" + cam.name; - gst_rtsp_mount_points_add_factory(mounts, mount_point.c_str(), cam.factory); - std::cout << "Camera '" << cam.name << "' available at rtsp://localhost:8554" << mount_point << std::endl; - } - - // 释放挂载点引用 - g_object_unref(mounts); - - // 附加服务器到默认主上下文 - loop = g_main_loop_new(nullptr, FALSE); - gst_rtsp_server_attach(server, nullptr); - - std::cout << "RTSP server running on rtsp://localhost:8554" << std::endl; - std::cout << "Press Ctrl+C to stop" << std::endl; - - g_main_loop_run(loop); // 运行主循环 - - // 清理资源 - g_object_unref(server); - g_main_loop_unref(loop); - - std::cout << "RTSP server stopped." << std::endl; - return 0; -} \ No newline at end of file diff --git a/src/rtsp_manager.cpp b/src/rtsp_manager.cpp new file mode 100644 index 0000000..5e002da --- /dev/null +++ b/src/rtsp_manager.cpp @@ -0,0 +1,72 @@ +// rtsp_manager.cpp +#include "rtsp_manager.hpp" +#include "logger.hpp" +#include + +GMainLoop *RTSPManager::loop = nullptr; +GstRTSPServer *RTSPManager::server = nullptr; + +void RTSPManager::init() +{ + gst_init(nullptr, nullptr); + LOG_INFO("[RTSP] GStreamer initialized."); +} + +GstRTSPMediaFactory *RTSPManager::create_media_factory(const Camera &cam) +{ + std::string launch_str = + "( v4l2src device=" + cam.device + + " ! video/x-raw,format=NV12,width=" + std::to_string(cam.width) + + ",height=" + std::to_string(cam.height) + + ",framerate=" + std::to_string(cam.fps) + "/1" + " ! videoconvert ! queue ! mpph264enc ! rtph264pay name=pay0 pt=96 )"; + + GstRTSPMediaFactory *factory = gst_rtsp_media_factory_new(); + gst_rtsp_media_factory_set_launch(factory, launch_str.c_str()); + gst_rtsp_media_factory_set_shared(factory, TRUE); + + return factory; +} + +void RTSPManager::start(const std::vector &cameras) +{ + server = gst_rtsp_server_new(); + gst_rtsp_server_set_service(server, "8554"); + + GstRTSPMountPoints *mounts = gst_rtsp_server_get_mount_points(server); + + for (auto &cam : cameras) + { + if (!cam.enabled) + continue; + + auto factory = create_media_factory(cam); + std::string mount_point = "/" + cam.name; + gst_rtsp_mount_points_add_factory(mounts, mount_point.c_str(), factory); + + LOG_INFO("[RTSP] Camera '" + cam.name + "' available at rtsp://localhost:8554" + mount_point); + } + + g_object_unref(mounts); + + loop = g_main_loop_new(nullptr, FALSE); + gst_rtsp_server_attach(server, nullptr); + + LOG_INFO("[RTSP] Server running on rtsp://localhost:8554"); + g_main_loop_run(loop); +} + +void RTSPManager::stop() +{ + if (server) + { + g_object_unref(server); + server = nullptr; + } + if (loop) + { + g_main_loop_unref(loop); + loop = nullptr; + } + LOG_INFO("[RTSP] Server stopped."); +}