first commit

This commit is contained in:
cxh 2025-09-08 14:55:07 +08:00
parent edc7cfc956
commit 0d22e2b95f
12 changed files with 377 additions and 324 deletions

68
.vscode/settings.json vendored Normal file
View File

@ -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"
}
}

View File

@ -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",

160
include/app_config.hpp Normal file
View File

@ -0,0 +1,160 @@
// app_config.hpp
#pragma once
#include <string>
#include <map>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <unistd.h>
#include <limits.h>
#include <stdexcept>
#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<Camera> 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;

View File

@ -1,3 +1,4 @@
// logger.hpp
#pragma once
#include <iostream>

View File

@ -1,55 +0,0 @@
#pragma once
#include <string>
#include <map>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <unistd.h>
#include <limits.h>
#include <stdexcept>
#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;

21
include/rtsp_manager.hpp Normal file
View File

@ -0,0 +1,21 @@
// rtsp_manager.hpp
#pragma once
#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-server.h>
#include "app_config.hpp"
// RTSP 管理器,负责启动/关闭 RTSP 服务器
class RTSPManager
{
public:
static void init();
static void start(const std::vector<Camera> &cameras);
static void stop();
private:
static GMainLoop *loop;
static GstRTSPServer *server;
static GstRTSPMediaFactory *create_media_factory(const Camera &cam);
};

5
src/app_config.cpp Normal file
View File

@ -0,0 +1,5 @@
// app_config.cpp
#include "app_config.hpp"
// 定义全局配置变量
AppConfig g_app_config;

View File

@ -1,3 +1,4 @@
// logger.cpp
#include "logger.hpp"
#include <iomanip>
#include <ctime>

38
src/main.cpp Normal file
View File

@ -0,0 +1,38 @@
// main.cpp
#include "app_config.hpp"
#include "rtsp_manager.hpp"
#include "logger.hpp"
#include <atomic>
#include <csignal>
std::atomic<bool> 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;
}

View File

@ -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<std::string>() + id;
cfg.topics.uplink_2 = j["topics"]["uplink_2"].get<std::string>() + id;
cfg.topics.downlink = j["topics"]["downlink"].get<std::string>() + 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;
}

View File

@ -1,145 +0,0 @@
// camera_to_rtsp.cpp
#include <gst/gst.h>
#include <gst/rtsp-server/rtsp-server.h>
#include <iostream>
#include <vector>
#include <atomic>
#include <thread>
#include <csignal>
#include <fstream>
#include <nlohmann/json.hpp>
#include <mutex>
using json = nlohmann::json;
GMainLoop *loop = nullptr;
// 全局退出标志
std::atomic<bool> 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<Camera> 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;
}

72
src/rtsp_manager.cpp Normal file
View File

@ -0,0 +1,72 @@
// rtsp_manager.cpp
#include "rtsp_manager.hpp"
#include "logger.hpp"
#include <iostream>
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<Camera> &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.");
}