This commit is contained in:
cxh 2025-12-17 15:50:48 +08:00
parent 67742b59ee
commit 3d66c2ae1b

View File

@ -9,6 +9,7 @@
#include <chrono>
#include <iostream>
#include <thread>
#include <unordered_map>
#include "logger.hpp"
@ -21,9 +22,14 @@ std::unordered_map<std::string, bool> RTSPManager::streaming_status;
std::unordered_map<std::string, GstRTSPMediaFactory*> RTSPManager::mounted_factories;
std::mutex RTSPManager::mounted_factories_mutex;
std::unordered_map<std::string, std::vector<GstRTSPMedia*>> RTSPManager::media_map;
std::mutex RTSPManager::media_map_mutex;
// ==============================
// ✅ 不再保存 GstRTSPMedia* 裸指针
// 改为:每路 camera 的“活跃 session 计数”
// ==============================
static std::unordered_map<std::string, int> g_client_count;
static std::mutex g_client_count_mutex;
// 日志降噪media-configure 过于频繁时只提示
static std::unordered_map<std::string, std::chrono::steady_clock::time_point> last_media_ts;
static std::mutex last_media_ts_mutex;
@ -98,11 +104,6 @@ GstRTSPMediaFactory* RTSPManager::create_media_factory(const Camera& cam)
std::to_string(w) + ",height=" + std::to_string(h) + ",framerate=" + std::to_string(cam.fps) + "/1";
// 关键tee 一路给 RTSP一路给 fakesink保持 v4l2src / mpph264enc 永远在跑
//
// 结构:
// v4l2src -> caps -> tee name=t
// tee src0: t. -> queue -> mpph264enc -> h264parse -> rtph264pay (给 RTSP)
// tee src1: t. -> queue -> fakesink (假消费者,让 DMA 持续 dequeue)
std::string launch_str = "( v4l2src device=" + cam.device +
" io-mode=2 is-live=true do-timestamp=true"
" ! " +
@ -125,7 +126,7 @@ GstRTSPMediaFactory* RTSPManager::create_media_factory(const Camera& cam)
// 所有客户端共享同一个 pipeline避免频繁拉起 v4l2 / encoder
gst_rtsp_media_factory_set_shared(factory, TRUE);
// 客户端断开时不要乱 reset,交给我们自己处理 / 或干脆不动
// 客户端断开时不要乱 suspend/reset
gst_rtsp_media_factory_set_suspend_mode(factory, GST_RTSP_SUSPEND_MODE_NONE);
g_signal_connect_data(factory, "media-configure", G_CALLBACK(on_media_created), g_strdup(cam.name.c_str()),
@ -139,7 +140,7 @@ void RTSPManager::start(const std::vector<Camera>& cams)
server = gst_rtsp_server_new();
gst_rtsp_server_set_service(server, "8554");
// ✅ attach 之前设置 backlog(限制 pending 连接队列)
// ✅ attach 之前设置 backlog
gst_rtsp_server_set_backlog(server, 32);
loop = g_main_loop_new(nullptr, FALSE);
@ -162,6 +163,12 @@ void RTSPManager::start(const std::vector<Camera>& cams)
streaming_status[cam.name] = true;
}
// 初始化计数
{
std::lock_guard<std::mutex> lock(g_client_count_mutex);
g_client_count.emplace(cam.name, 0);
}
LOG_INFO("[RTSP] Camera '" + cam.name + "' mounted at rtsp://0.0.0.0:8554" + mount_point);
}
@ -190,52 +197,62 @@ void RTSPManager::on_media_created(GstRTSPMediaFactory*, GstRTSPMedia* media, gp
{
const char* cam_name = static_cast<const char*>(user_data);
bool suppressed = false;
// ✅ 只做日志降噪:别让“抑制”影响生命周期处理
bool noisy = false;
auto now = std::chrono::steady_clock::now();
{
std::lock_guard<std::mutex> lock(last_media_ts_mutex);
auto& last = last_media_ts[cam_name];
if (last.time_since_epoch().count() != 0 && now - last < std::chrono::seconds(2))
{
suppressed = true; // 只标记,不 return
}
if (last.time_since_epoch().count() != 0 && now - last < std::chrono::seconds(2)) noisy = true;
last = now;
}
if (suppressed) { LOG_WARN(std::string("[RTSP] media-configure suppressed (too frequent): ") + cam_name); }
int count_after = 0;
{
std::lock_guard<std::mutex> lock(g_client_count_mutex);
int& c = g_client_count[cam_name]; // 若不存在会创建;这里无所谓
c++;
count_after = c;
}
if (noisy)
{
LOG_WARN(std::string("[RTSP] media-configure frequent: ") + cam_name +
" (active_sessions=" + std::to_string(count_after) + ")");
}
else
{
LOG_INFO(std::string("[RTSP] media-configure for camera: ") + cam_name);
}
// ✅ 生命周期一定要完整绑定
{
std::lock_guard<std::mutex> lock(media_map_mutex);
media_map[cam_name].push_back(media);
LOG_INFO(std::string("[RTSP] media-configure for camera: ") + cam_name +
" (active_sessions=" + std::to_string(count_after) + ")");
}
// ✅ 生命周期绑定unprepared 一定会对应减计数
g_signal_connect_data(media, "unprepared", G_CALLBACK(on_media_unprepared), g_strdup(cam_name),
(GClosureNotify)g_free, (GConnectFlags)0);
}
void RTSPManager::on_media_unprepared(GstRTSPMedia* media, gpointer user_data)
void RTSPManager::on_media_unprepared(GstRTSPMedia* /*media*/, gpointer user_data)
{
const char* cam_name = static_cast<const char*>(user_data);
LOG_INFO(std::string("[RTSP] media-unprepared: ") + cam_name);
std::lock_guard<std::mutex> lock(media_map_mutex);
auto it = media_map.find(cam_name);
if (it != media_map.end())
int count_after = 0;
{
auto& vec = it->second;
vec.erase(std::remove(vec.begin(), vec.end(), media), vec.end());
if (vec.empty()) media_map.erase(it);
}
else
{
LOG_WARN(std::string("[RTSP] media-unprepared but no entry in media_map for camera: ") + cam_name);
std::lock_guard<std::mutex> lock(g_client_count_mutex);
auto it = g_client_count.find(cam_name);
if (it == g_client_count.end())
{
// 可能发生在 unmount 后 teardown 仍在进行的边界情况
LOG_WARN(std::string("[RTSP] media-unprepared but cam not in client_count: ") + cam_name);
return;
}
if (it->second > 0) it->second--;
count_after = it->second;
}
LOG_INFO(std::string("[RTSP] media-unprepared: ") + cam_name + " (active_sessions=" + std::to_string(count_after) +
")");
// ✅ 不要 g_object_unref(media) —— gst-rtsp-server 会处理
}
@ -262,6 +279,11 @@ gboolean RTSPManager::mount_camera_in_main(gpointer data)
streaming_status[cam->name] = true;
}
{
std::lock_guard<std::mutex> lock(g_client_count_mutex);
g_client_count[cam->name] = 0;
}
LOG_INFO("[RTSP] Camera '" + cam->name + "' mounted at rtsp://localhost:8554" + mount_point);
delete cam;
@ -280,14 +302,7 @@ gboolean RTSPManager::unmount_camera_in_main(gpointer data)
std::string cam_name = cam->name;
std::string mount_point = "/" + cam_name;
// ✅ 不要手动 gst_rtsp_media_unprepare / set_state(NULL)
// 只移除我们自己的记录。media 的 teardown 交给 gst-rtsp-server。
{
std::lock_guard<std::mutex> lock(media_map_mutex);
media_map.erase(cam_name);
}
// 移除 factory
// ✅ 仅移除 mountmedia teardown 交给 gst-rtsp-server
GstRTSPMountPoints* mounts = gst_rtsp_server_get_mount_points(server);
if (mounts)
{
@ -300,16 +315,17 @@ gboolean RTSPManager::unmount_camera_in_main(gpointer data)
auto it = mounted_factories.find(cam_name);
if (it != mounted_factories.end())
{
// ⚠️ 这里是否 unref factory 取决于 mount_points 是否持有引用。
// 一般 remove_factory 后会释放其引用;你再 unref 可能导致过度 unref。
// 为了稳,建议:先别手动 unref避免 double-unref。
// if (it->second) g_object_unref(it->second);
// 为了避免 double-unref这里不手动 unref factory
mounted_factories.erase(it);
}
streaming_status[cam_name] = false;
}
{
std::lock_guard<std::mutex> lock(g_client_count_mutex);
g_client_count.erase(cam_name);
}
LOG_INFO("[RTSP] Camera '" + cam_name + "' unmounted.");
delete cam;
return G_SOURCE_REMOVE;