This commit is contained in:
cxh 2025-11-25 09:13:52 +08:00
parent 1b4567d2d8
commit 53f496ca7e

View File

@ -82,7 +82,7 @@ void RTSPManager::init()
GstRTSPMediaFactory *RTSPManager::create_media_factory(const Camera &cam) GstRTSPMediaFactory *RTSPManager::create_media_factory(const Camera &cam)
{ {
// --- 设置一次 V4L2 格式 --- // 启动前把 v4l2 格式设成我们想要的
set_v4l2_format(cam.device, cam.width, cam.height); set_v4l2_format(cam.device, cam.width, cam.height);
int w = cam.width; int w = cam.width;
@ -95,26 +95,19 @@ GstRTSPMediaFactory *RTSPManager::create_media_factory(const Camera &cam)
",height=" + std::to_string(h) + ",height=" + std::to_string(h) +
",framerate=" + std::to_string(cam.fps) + "/1"; ",framerate=" + std::to_string(cam.fps) + "/1";
// -------------------------- // 注意几点:
// ★★★ 稳定管线(重点全在这里)★★★ // 1) 给 mpph264enc 起一个名字 name=enc方便后面在 on_media_created 里拿到
// // 2) option-force-idr / option-idr-interval 依然在这里设置一遍,保证周期 IDR
// 1) v4l2src is-live=true do-timestamp=true → 防止 preroll 卡死 // 3) config-interval=1 一定要写在 rtph264pay 上
// 2) queue max-size=0 → 避免首帧丢失
// 3) mpph264enc
// rc-mode=cbr bps=xxx → 保持稳定码率
// gop=fps → 1s 一个 I 帧
// option-force-idr=true → 强制 IDR 输出
// option-idr-interval=fps → VLC 必需
// 4) h264parse → 整理 NAL
// 5) rtph264pay config-interval=1 → 每秒 SPS+PPS最关键
// --------------------------
std::string launch_str = std::string launch_str =
"( v4l2src device=" + cam.device + "( v4l2src device=" + cam.device +
" io-mode=2 is-live=true do-timestamp=true" " io-mode=2 is-live=true do-timestamp=true"
" ! " + " ! " +
caps + caps +
" ! queue leaky=downstream max-size-time=0 max-size-bytes=0 max-size-buffers=0" " ! queue leaky=downstream max-size-time=0 max-size-bytes=0 max-size-buffers=0"
" ! mpph264enc rc-mode=cbr bps=" + " ! mpph264enc name=enc"
" rc-mode=cbr"
" bps=" +
std::to_string(cam.bitrate) + std::to_string(cam.bitrate) +
" gop=" + std::to_string(cam.fps) + " gop=" + std::to_string(cam.fps) +
" option-force-idr=true" " option-force-idr=true"
@ -124,24 +117,23 @@ GstRTSPMediaFactory *RTSPManager::create_media_factory(const Camera &cam)
" ! h264parse" " ! h264parse"
" ! rtph264pay name=pay0 pt=96 config-interval=1 )"; " ! rtph264pay name=pay0 pt=96 config-interval=1 )";
LOG_INFO("[RTSP] Launch: " + launch_str); LOG_INFO("[RTSP] Launch for " + cam.name + ": " + launch_str);
GstRTSPMediaFactory *factory = gst_rtsp_media_factory_new(); GstRTSPMediaFactory *factory = gst_rtsp_media_factory_new();
gst_rtsp_media_factory_set_launch(factory, launch_str.c_str()); gst_rtsp_media_factory_set_launch(factory, launch_str.c_str());
// shared=TRUE 依然能用,因为我们让编码器永远产帧 // 先保持 shared=TRUE节省资源
gst_rtsp_media_factory_set_shared(factory, TRUE); gst_rtsp_media_factory_set_shared(factory, TRUE);
// 不要在客户端断开时 RESET 管线(防止 maxim4c 重启) // 客户端断开时不重置 pipeline防止反复 s_stream(0/1)
gst_rtsp_media_factory_set_suspend_mode(factory, GST_RTSP_SUSPEND_MODE_NONE); gst_rtsp_media_factory_set_suspend_mode(factory, GST_RTSP_SUSPEND_MODE_NONE);
// 绑定事件
g_signal_connect_data(factory, g_signal_connect_data(factory,
"media-configure", "media-configure",
G_CALLBACK(on_media_created), G_CALLBACK(on_media_created),
g_strdup(cam.name.c_str()), g_strdup(cam.name.c_str()),
(GClosureNotify)g_free, (GClosureNotify)g_free,
GConnectFlags(0)); (GConnectFlags)0);
return factory; return factory;
} }
@ -205,9 +197,8 @@ void RTSPManager::on_media_created(GstRTSPMediaFactory *, GstRTSPMedia *media, g
LOG_INFO(std::string("[RTSP] media-configure for camera: ") + cam_name); LOG_INFO(std::string("[RTSP] media-configure for camera: ") + cam_name);
// media 自身加引用,存入 map // 把 media 保存起来,方便后面 unmount
g_object_ref(media); g_object_ref(media);
{ {
std::lock_guard<std::mutex> lock(media_map_mutex); std::lock_guard<std::mutex> lock(media_map_mutex);
media_map[cam_name].push_back(media); media_map[cam_name].push_back(media);
@ -221,10 +212,32 @@ void RTSPManager::on_media_created(GstRTSPMediaFactory *, GstRTSPMedia *media, g
(GClosureNotify)g_free, (GClosureNotify)g_free,
(GConnectFlags)0); (GConnectFlags)0);
// 强制 pipeline 进入 PLAYING避免 preroll 阶段卡死在 PAUSED // 获取底层 pipeline
GstElement *pipeline = gst_rtsp_media_get_element(media); GstElement *pipeline = gst_rtsp_media_get_element(media);
if (pipeline) if (!pipeline)
{ {
LOG_ERROR(std::string("[RTSP] Pipeline is NULL for camera: ") + cam_name);
return;
}
// ★★★ 关键:拿到 encoder 元素,强制打一发 IDR保证 VLC 第一次连接就能拿到关键帧
GstElement *enc = gst_bin_get_by_name(GST_BIN(pipeline), "enc");
if (enc)
{
LOG_INFO(std::string("[RTSP] Forcing IDR for camera: ") + cam_name);
// 这里用和 launch 里同名的属性,确保 Rockchip mpph264enc 能识别
g_object_set(enc, "option-force-idr", TRUE, NULL);
// 如果你想更狠一点,也可以顺便再 set 一次 idr-interval
// g_object_set(enc, "option-idr-interval", cam.fps, NULL); // 这里需要你把 fps 传进来才行
gst_object_unref(enc);
}
else
{
LOG_WARN(std::string("[RTSP] Encoder 'enc' not found in pipeline for camera: ") + cam_name);
}
// 强制 pipeline 进入 PLAYING避免停在 PAUSED/preroll
GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) if (ret == GST_STATE_CHANGE_FAILURE)
{ {
@ -235,13 +248,8 @@ void RTSPManager::on_media_created(GstRTSPMediaFactory *, GstRTSPMedia *media, g
LOG_INFO(std::string("[RTSP] Force pipeline PLAYING for camera: ") + cam_name); LOG_INFO(std::string("[RTSP] Force pipeline PLAYING for camera: ") + cam_name);
} }
// ⚠️ 这里一定要 unref一次 get_element 一次 unref // gst_rtsp_media_get_element() 返回的是加过 ref 的对象,需要 unref 一次
gst_object_unref(pipeline); gst_object_unref(pipeline);
}
else
{
LOG_ERROR(std::string("[RTSP] Pipeline is NULL for camera: ") + cam_name);
}
} }
void RTSPManager::on_media_unprepared(GstRTSPMedia *media, gpointer user_data) void RTSPManager::on_media_unprepared(GstRTSPMedia *media, gpointer user_data)