diff --git a/src/rtsp_manager.cpp b/src/rtsp_manager.cpp index 802eaac..6bfa55b 100644 --- a/src/rtsp_manager.cpp +++ b/src/rtsp_manager.cpp @@ -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 lock(media_map_mutex); media_map[cam_name].push_back(media); @@ -221,27 +212,44 @@ 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) { - GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); - if (ret == GST_STATE_CHANGE_FAILURE) - { - LOG_ERROR(std::string("[RTSP] Failed to set pipeline PLAYING for camera: ") + cam_name); - } - else - { - LOG_INFO(std::string("[RTSP] Force pipeline PLAYING for camera: ") + cam_name); - } + LOG_ERROR(std::string("[RTSP] Pipeline is NULL for camera: ") + cam_name); + return; + } - // ⚠️ 这里一定要 unref,一次 get_element 一次 unref - gst_object_unref(pipeline); + // ★★★ 关键:拿到 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_ERROR(std::string("[RTSP] Pipeline is NULL for camera: ") + cam_name); + 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) + { + LOG_ERROR(std::string("[RTSP] Failed to set pipeline PLAYING for camera: ") + cam_name); + } + else + { + LOG_INFO(std::string("[RTSP] Force pipeline PLAYING for camera: ") + cam_name); + } + + // gst_rtsp_media_get_element() 返回的是加过 ref 的对象,需要 unref 一次 + gst_object_unref(pipeline); } void RTSPManager::on_media_unprepared(GstRTSPMedia *media, gpointer user_data)