yituo_video/src/rtsp_manager.cpp
2025-12-17 15:50:48 +08:00

383 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// rtsp_manager.cpp
#include "rtsp_manager.hpp"
#include <fcntl.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <algorithm>
#include <chrono>
#include <iostream>
#include <thread>
#include <unordered_map>
#include "logger.hpp"
// 静态变量定义
GMainLoop* RTSPManager::loop = nullptr;
GMainContext* RTSPManager::main_context = nullptr;
GstRTSPServer* RTSPManager::server = nullptr;
std::unordered_map<std::string, bool> RTSPManager::streaming_status;
std::unordered_map<std::string, GstRTSPMediaFactory*> RTSPManager::mounted_factories;
std::mutex RTSPManager::mounted_factories_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;
bool set_v4l2_format(const std::string& dev, int width, int height)
{
int fd = open(dev.c_str(), O_RDWR);
if (fd < 0)
{
LOG_ERROR("Failed to open " + dev);
return false;
}
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
// 读格式,若相同则略过
if (ioctl(fd, VIDIOC_G_FMT, &fmt) == 0)
{
bool match = true;
if (fmt.fmt.pix_mp.width != (unsigned int)width || fmt.fmt.pix_mp.height != (unsigned int)height ||
fmt.fmt.pix_mp.pixelformat != V4L2_PIX_FMT_NV12)
{
match = false;
}
if (match)
{
close(fd);
LOG_INFO("[RTSP] V4L2 format already NV12 " + std::to_string(width) + "x" + std::to_string(height) +
" for " + dev);
return true;
}
}
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = width;
fmt.fmt.pix_mp.height = height;
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
fmt.fmt.pix_mp.num_planes = 1;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0)
{
LOG_ERROR("VIDIOC_S_FMT failed for " + dev);
close(fd);
return false;
}
LOG_INFO("[RTSP] Set V4L2 format to NV12 " + std::to_string(width) + "x" + std::to_string(height) + " for " + dev);
close(fd);
return true;
}
void RTSPManager::init()
{
gst_init(nullptr, nullptr);
LOG_INFO("[RTSP] GStreamer initialized.");
}
GstRTSPMediaFactory* RTSPManager::create_media_factory(const Camera& cam)
{
// 启动前把 v4l2 格式设成我们想要的
set_v4l2_format(cam.device, cam.width, cam.height);
int w = cam.width;
int h = cam.height;
std::string caps =
"video/x-raw,format=NV12,"
"width=" +
std::to_string(w) + ",height=" + std::to_string(h) + ",framerate=" + std::to_string(cam.fps) + "/1";
// 关键tee 一路给 RTSP一路给 fakesink保持 v4l2src / mpph264enc 永远在跑
std::string launch_str = "( v4l2src device=" + cam.device +
" io-mode=2 is-live=true do-timestamp=true"
" ! " +
caps +
" ! tee name=t "
" ! queue leaky=downstream max-size-time=0 max-size-bytes=0 max-size-buffers=0"
" ! mpph264enc name=enc rc-mode=cbr bps=" +
std::to_string(cam.bitrate) + " gop=" + std::to_string(cam.fps) +
" header-mode=1"
" ! h264parse"
" ! rtph264pay name=pay0 pt=96 config-interval=1 "
" t. ! queue leaky=downstream max-size-buffers=3 max-size-bytes=0 max-size-time=0"
" ! fakesink sync=false async=false )";
LOG_INFO("[RTSP] Launch for " + cam.name + ": " + launch_str);
GstRTSPMediaFactory* factory = gst_rtsp_media_factory_new();
gst_rtsp_media_factory_set_launch(factory, launch_str.c_str());
// 所有客户端共享同一个 pipeline避免频繁拉起 v4l2 / encoder
gst_rtsp_media_factory_set_shared(factory, TRUE);
// 客户端断开时不要乱 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()),
(GClosureNotify)g_free, (GConnectFlags)0);
return factory;
}
void RTSPManager::start(const std::vector<Camera>& cams)
{
server = gst_rtsp_server_new();
gst_rtsp_server_set_service(server, "8554");
// ✅ attach 之前设置 backlog
gst_rtsp_server_set_backlog(server, 32);
loop = g_main_loop_new(nullptr, FALSE);
main_context = g_main_loop_get_context(loop);
GstRTSPMountPoints* mounts = gst_rtsp_server_get_mount_points(server);
for (const auto& cam : cams)
{
if (!cam.enabled) continue;
GstRTSPMediaFactory* factory = create_media_factory(cam);
std::string mount_point = "/" + cam.name;
gst_rtsp_mount_points_add_factory(mounts, mount_point.c_str(), factory);
{
std::lock_guard<std::mutex> lock(mounted_factories_mutex);
mounted_factories[cam.name] = factory;
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);
}
g_object_unref(mounts);
gst_rtsp_server_attach(server, nullptr);
LOG_INFO("[RTSP] Server running on rtsp://0.0.0.0:8554");
g_main_loop_run(loop);
if (server)
{
g_object_unref(server);
server = nullptr;
}
if (loop)
{
g_main_loop_unref(loop);
loop = nullptr;
}
LOG_INFO("[RTSP] Server stopped.");
}
void RTSPManager::on_media_created(GstRTSPMediaFactory*, GstRTSPMedia* media, gpointer user_data)
{
const char* cam_name = static_cast<const char*>(user_data);
// ✅ 只做日志降噪:别让“抑制”影响生命周期处理
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)) noisy = true;
last = now;
}
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 +
" (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)
{
const char* cam_name = static_cast<const char*>(user_data);
int count_after = 0;
{
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 会处理
}
gboolean RTSPManager::mount_camera_in_main(gpointer data)
{
Camera* cam = static_cast<Camera*>(data);
if (!cam || !server)
{
delete cam;
return G_SOURCE_REMOVE;
}
GstRTSPMountPoints* mounts = gst_rtsp_server_get_mount_points(server);
std::string mount_point = "/" + cam->name;
GstRTSPMediaFactory* factory = create_media_factory(*cam);
gst_rtsp_mount_points_add_factory(mounts, mount_point.c_str(), factory);
g_object_unref(mounts);
{
std::lock_guard<std::mutex> lock(mounted_factories_mutex);
mounted_factories[cam->name] = factory;
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;
return G_SOURCE_REMOVE;
}
gboolean RTSPManager::unmount_camera_in_main(gpointer data)
{
Camera* cam = static_cast<Camera*>(data);
if (!cam || !server)
{
delete cam;
return G_SOURCE_REMOVE;
}
std::string cam_name = cam->name;
std::string mount_point = "/" + cam_name;
// ✅ 仅移除 mountmedia teardown 交给 gst-rtsp-server
GstRTSPMountPoints* mounts = gst_rtsp_server_get_mount_points(server);
if (mounts)
{
gst_rtsp_mount_points_remove_factory(mounts, mount_point.c_str());
g_object_unref(mounts);
}
{
std::lock_guard<std::mutex> lock(mounted_factories_mutex);
auto it = mounted_factories.find(cam_name);
if (it != mounted_factories.end())
{
// 为了避免 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;
}
void RTSPManager::mount_camera(const Camera& cam)
{
Camera* camCopy = new Camera(cam);
g_main_context_invoke(
main_context, [](gpointer data) -> gboolean { return RTSPManager::mount_camera_in_main(data); }, camCopy);
}
void RTSPManager::unmount_camera(const Camera& cam)
{
Camera* camCopy = new Camera(cam);
g_main_context_invoke(
main_context, [](gpointer data) -> gboolean { return RTSPManager::unmount_camera_in_main(data); }, camCopy);
}
bool RTSPManager::is_streaming(const std::string& cam_name)
{
std::lock_guard<std::mutex> lock(mounted_factories_mutex);
auto it = streaming_status.find(cam_name);
return it != streaming_status.end() ? it->second : false;
}
bool RTSPManager::is_any_streaming()
{
std::lock_guard<std::mutex> lock(mounted_factories_mutex);
for (auto& kv : streaming_status)
if (kv.second) return true;
return false;
}
void RTSPManager::stop()
{
if (!loop) return;
if (server) gst_rtsp_server_set_backlog(server, 0); // optional
if (main_context)
{
g_main_context_invoke(
main_context,
[](gpointer data) -> gboolean
{
g_main_loop_quit(static_cast<GMainLoop*>(data));
return G_SOURCE_REMOVE;
},
loop);
}
g_main_loop_quit(loop);
}