1
This commit is contained in:
parent
1b4567d2d8
commit
53f496ca7e
@ -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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user