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)
{
// --- 设置一次 V4L2 格式 ---
// 启动前把 v4l2 格式设成我们想要的
set_v4l2_format(cam.device, cam.width, cam.height);
int w = cam.width;
@ -95,26 +95,19 @@ GstRTSPMediaFactory *RTSPManager::create_media_factory(const Camera &cam)
",height=" + std::to_string(h) +
",framerate=" + std::to_string(cam.fps) + "/1";
// --------------------------
// ★★★ 稳定管线(重点全在这里)★★★
//
// 1) v4l2src is-live=true do-timestamp=true → 防止 preroll 卡死
// 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最关键
// --------------------------
// 注意几点:
// 1) 给 mpph264enc 起一个名字 name=enc方便后面在 on_media_created 里拿到
// 2) option-force-idr / option-idr-interval 依然在这里设置一遍,保证周期 IDR
// 3) config-interval=1 一定要写在 rtph264pay 上
std::string launch_str =
"( v4l2src device=" + cam.device +
" io-mode=2 is-live=true do-timestamp=true"
" ! " +
caps +
" ! 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) +
" gop=" + std::to_string(cam.fps) +
" option-force-idr=true"
@ -124,24 +117,23 @@ GstRTSPMediaFactory *RTSPManager::create_media_factory(const Camera &cam)
" ! h264parse"
" ! 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();
gst_rtsp_media_factory_set_launch(factory, launch_str.c_str());
// shared=TRUE 依然能用,因为我们让编码器永远产帧
// 先保持 shared=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);
// 绑定事件
g_signal_connect_data(factory,
"media-configure",
G_CALLBACK(on_media_created),
g_strdup(cam.name.c_str()),
(GClosureNotify)g_free,
GConnectFlags(0));
(GConnectFlags)0);
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);
// media 自身加引用,存入 map
// 把 media 保存起来,方便后面 unmount
g_object_ref(media);
{
std::lock_guard<std::mutex> lock(media_map_mutex);
media_map[cam_name].push_back(media);
@ -221,10 +212,32 @@ void RTSPManager::on_media_created(GstRTSPMediaFactory *, GstRTSPMedia *media, g
(GClosureNotify)g_free,
(GConnectFlags)0);
// 强制 pipeline 进入 PLAYING避免 preroll 阶段卡死在 PAUSED
// 获取底层 pipeline
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);
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);
}
// ⚠️ 这里一定要 unref一次 get_element 一次 unref
// gst_rtsp_media_get_element() 返回的是加过 ref 的对象,需要 unref 一次
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)