You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GStreamer rtspsrc切换32次后失效,如何不重启释放资源?

问题描述

我有一台运行嵌入式Linux的设备,能显示摄像头的RTSP流,用户可在窗口模式与全屏模式间切换流。但切换32次后,流停止工作,初步定位问题出在rtspsrc本身。

核心疑问:如何在不重启程序的情况下清理GStreamer相关内存?

现象细节:

  • 用gst-launch-1.0启动管道时,因每次都会终止程序,可正常重启超过32次;但在程序内切换流,当rtspsrc编号递增至31后,再用gst-launch-1.0启动RTSP管道也无法显示流,必须终止所有使用GStreamer的程序才能重置rtspsrc计数。
  • 开启rtspsrc调试(export GST_DEBUG="rtspsrc:6")后,每次启动流都会输出日志,显示rtspsrcX编号持续递增,即便前序流已停止:
    首次运行日志:
    **rtspsrc gstrtspsrc.c:8834:gst_rtspsrc_print_sdp_media:<rtspsrc0> RTSP response message**
    第二次运行:
    **rtspsrc gstrtspsrc.c:8855:gst_rtspsrc_print_sdp_media:<rtspsrc1>  RTSP response message**
    持续启停流,编号会递增至31,此时流不再显示:
    **rtspsrc gstrtspsrc.c:8855:gst_rtspsrc_print_sdp_media:<rtspsrc31>  RTSP response message**
    

尝试过的操作:

  • 每次重启流时新建GMainContext,但无效;每次调用gst_is_initialized()均返回true。
  • 停止流时通过另一线程调用g_main_loop_quit(loop_),并在stream_video函数内依次将管道状态切换到PAUSED、READY、NULL,再释放管道、上下文和主循环对象,但rtspsrc编号仍持续增长。

程序核心代码:

GMainLoop *loop_;

// 两种管道配置,对应窗口和全屏模式
std::string pipeline_win = "rtspsrc location=rtsp://192.168.0.243/0 latency=0 ! rtph264depay ! h264parse ! imxvpudec ! imxipuvideosink window-width=512 window-height=384 sync=false";
std::string pipeline_full = "rtspsrc location=rtsp://192.168.0.243/0 latency=0 ! rtph264depay ! h264parse ! imxvpudec ! imxipuvideosink window-width=1024 window-height=768 sync=false";

void stream_video(std::string pipeline)
{
    GMainContext* context;
        
    GstElement *pipelineElement;
    GstBus *bus = NULL;
    guint bus_watch_id = 0;
    GstState state;

    try
    {
        if(!gst_is_initialized()) 
        {
            std::cout << "GST Is not initialized - initializing " << pipeline.c_str();
            gst_init_check(nullptr,nullptr,nullptr);
        }

        context = g_main_context_new();    // 尝试新建上下文解决32次限制,但调试时rtspsrc编号仍递增
        loop_ = g_main_loop_new (context, FALSE);

        pipelineElement = gst_parse_launch(pipeline.c_str(), NULL);

        bus = gst_pipeline_get_bus (GST_PIPELINE (pipelineElement));
        bus_watch_id = gst_bus_add_watch (bus, bus_call, loop_);
        gst_object_unref (bus);
        bus = NULL;

        gst_element_set_state(pipelineElement, GST_STATE_READY );
        gst_element_set_state(pipelineElement, GST_STATE_PAUSED );
        gst_element_set_state(pipelineElement, GST_STATE_PLAYING);

        if (gst_element_get_state (pipelineElement, &state, NULL, 2*GST_SECOND) == GST_STATE_CHANGE_FAILURE)
        {
            std::cout <<  "gst: Failed to change states State:" << state << " ID: " << stream_id_;
        }
        else
        {
            std::cout << "gst: Running..." << " ID: " << stream_id_ << " State:" << state << " Loop:" << loop_;
            g_main_loop_run (loop_); // 阻塞直到loop退出(EOS、错误、停止请求)
        }
        // 停止流时切换状态
        gst_element_set_state(pipelineElement, GST_STATE_PAUSED);
        gst_element_set_state(pipelineElement, GST_STATE_READY );
        gst_element_set_state(pipelineElement, GST_STATE_NULL);    // 状态切换需遵循GStreamer状态机规则

        g_source_remove (bus_watch_id);

        std::cout << "gst: Removing pipelineElement " << pipelineElement;
        gst_object_unref (GST_OBJECT (pipelineElement));
        pipelineElement = NULL;

        
        g_main_context_unref (context);
        context = NULL;

        g_main_loop_unref (loop_);
        loop_ = nullptr;    

        std::cout << "gst: Deleted pipeline" << " ID: " << stream_id_ << " State: " << state;
    }
    catch(const std::exception& e)
    {
        std::cout << "Error Caught: stream_video " << e.what();
    }
    return;
}

解决方案

1. 确保RTSP资源彻底释放

rtspsrc内部维护网络连接、会话等资源,32次限制大概率是因为停止流时部分底层资源(如socket、RTSP会话句柄)未正确回收。优化步骤:

  • 切换到GST_STATE_NULL后,等待状态切换完成再释放资源,避免资源泄漏:
    // 切换到NULL状态后等待确认
    if (gst_element_get_state(pipelineElement, NULL, NULL, 5*GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
        std::cout << "Warning: Failed to set pipeline to NULL state" << std::endl;
    }
    
  • 给rtspsrc添加close-socket=true属性,强制停止时立即关闭socket:
    修改管道字符串:
    rtspsrc location=rtsp://192.168.0.243/0 latency=0 close-socket=true ! rtph264depay ! h264parse ! imxvpudec ! imxipuvideosink ...
    

2. 复用管道而非每次重建

频繁创建销毁管道会增加资源泄漏风险,建议复用核心管道元素,仅修改视频输出窗口大小:

  • 初始化时创建一次完整管道,保存rtspsrc、解码链和imxipuvideosink的引用
  • 切换窗口/全屏时,直接修改sink的窗口属性:
    // 假设已保存videosink元素引用
    g_object_set(videosink, "window-width", 1024, "window-height", 768, NULL);
    
    这种方式从根源上避免rtspsrc编号递增问题。

3. 完善总线消息处理

确保bus_call函数正确处理所有总线消息(尤其是ERROR和EOS),避免未处理消息导致资源挂起:

static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
    GMainLoop *loop = (GMainLoop *) data;

    switch (GST_MESSAGE_TYPE(msg)) {
        case GST_MESSAGE_ERROR: {
            GError *err;
            gchar *debug;
            gst_message_parse_error(msg, &err, &debug);
            g_printerr("Error: %s\n", err->message);
            g_error_free(err);
            g_free(debug);
            g_main_loop_quit(loop);
            break;
        }
        case GST_MESSAGE_EOS:
            g_main_loop_quit(loop);
            break;
        default:
            break;
    }
    return TRUE;
}

4. 强制重置GStreamer全局资源(极端场景)

若以上方法无效,可在每次停止流后调用gst_deinit()彻底重置GStreamer,下次启动时重新初始化:

// 销毁管道、上下文和主循环后执行
gst_deinit();

注意:该操作会清理所有GStreamer全局状态,可能影响其他GStreamer模块,单流场景可使用。

5. 排查嵌入式平台资源限制

嵌入式Linux默认进程文件描述符上限可能为32,刚好匹配你的问题现象。排查方法:

# 查看进程文件描述符使用量
lsof -p <进程PID> | wc -l
# 查看系统文件描述符限制
ulimit -n

若为文件描述符耗尽,可提高进程限制:

#include <sys/resource.h>
struct rlimit rlim;
getrlimit(RLIMIT_NOFILE, &rlim);
rlim.rlim_cur = 128; // 调整为合适值
setrlimit(RLIMIT_NOFILE, &rlim);

内容的提问来源于stack exchange,提问作者Luke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 08:24:20