first commit
This commit is contained in:
parent
edc7cfc956
commit
0d22e2b95f
68
.vscode/settings.json
vendored
Normal file
68
.vscode/settings.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
config.json
11
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": [
|
"cameras": [
|
||||||
{
|
{
|
||||||
"device": "/dev/video11",
|
"device": "/dev/video11",
|
||||||
|
|||||||
160
include/app_config.hpp
Normal file
160
include/app_config.hpp
Normal 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;
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
// logger.hpp
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|||||||
@ -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
21
include/rtsp_manager.hpp
Normal 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
5
src/app_config.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// app_config.cpp
|
||||||
|
#include "app_config.hpp"
|
||||||
|
|
||||||
|
// 定义全局配置变量
|
||||||
|
AppConfig g_app_config;
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
// logger.cpp
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|||||||
38
src/main.cpp
Normal file
38
src/main.cpp
Normal 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;
|
||||||
|
}
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
145
src/rtsp.cpp
145
src/rtsp.cpp
@ -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
72
src/rtsp_manager.cpp
Normal 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.");
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user