GStreamer实现MP4视频RTSP循环推流失败问题求助
问题:GStreamer RTSP服务器循环推流MP4失败,无法回退或切换视频
我修改了GStreamer gst-rtsp-server的test-appsrc2.c示例代码,想要实现RTSP循环推流MP4视频。思路是在生成管道收到EOS(流结束)信号后重启管道。目前已经能成功接收EOS信号,但既无法回退管道重新播放原视频,也无法切换播放其他视频,请问问题出在哪里?
代码实现
#include <gst/gst.h> #include <gst/app/app.h> #include <gst/rtsp-server/rtsp-server.h> #include <thread> typedef struct { GstElement *generator_pipe; GstElement *vid_appsink; GstElement *vid_appsrc; GstElement *aud_appsink; GstElement *aud_appsrc; } MyContext; gboolean bus_callback(GstBus *bus, GstMessage *msg, gpointer data) { using namespace std::chrono_literals; GstElement *vin; GstElement *pipeline = GST_ELEMENT(data); switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: g_print("GST_MESSAGE_EOS: %s\n", gst_message_type_get_name (GST_MESSAGE_TYPE (msg))); gst_element_set_state(pipeline, GST_STATE_NULL); std::this_thread::sleep_for(100ms); vin = gst_bin_get_by_name (GST_BIN (pipeline), "vin"); g_print ("vin: %p\n",vin); if(vin) { gst_util_set_object_arg (G_OBJECT (vin), "location", "video02.mp4"); } /* if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, // 1 seconds (in nanoseconds) GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { g_print("Seek failed!\n"); } */ gst_element_set_state(pipeline, GST_STATE_PLAYING); break; case GST_MESSAGE_STATE_CHANGED: break; default: g_print("got message %s\n", gst_message_type_get_name (GST_MESSAGE_TYPE (msg))); break; } return TRUE; } /* called when we need to give data to an appsrc */ static void need_data (GstElement * appsrc, guint unused, MyContext * ctx) { GstSample *sample; GstFlowReturn ret; sample = gst_app_sink_pull_sample (GST_APP_SINK (ctx->vid_appsink)); if (sample) { GstBuffer *buffer = gst_sample_get_buffer (sample); GstSegment *seg = gst_sample_get_segment (sample); GstClockTime pts, dts; /* Convert the PTS/DTS to running time so they start from 0 */ pts = GST_BUFFER_PTS (buffer); if (GST_CLOCK_TIME_IS_VALID (pts)) pts = gst_segment_to_running_time (seg, GST_FORMAT_TIME, pts); dts = GST_BUFFER_DTS (buffer); if (GST_CLOCK_TIME_IS_VALID (dts)) dts = gst_segment_to_running_time (seg, GST_FORMAT_TIME, dts); if (buffer) { /* Make writable so we can adjust the timestamps */ buffer = gst_buffer_copy (buffer); GST_BUFFER_PTS (buffer) = pts; GST_BUFFER_DTS (buffer) = dts; g_signal_emit_by_name (appsrc, "push-buffer", buffer, &ret); } /* we don't need the appsink sample anymore */ gst_sample_unref (sample); } } static void ctx_free (MyContext * ctx) { g_print ("ctx_free\n"); gst_element_set_state (ctx->generator_pipe, GST_STATE_NULL); gst_object_unref (ctx->generator_pipe); gst_object_unref (ctx->vid_appsrc); gst_object_unref (ctx->vid_appsink); g_free (ctx); } /* called when a new media pipeline is constructed. We can query the * pipeline and configure our appsrc */ static void media_configure (GstRTSPMediaFactory * factory, GstRTSPMedia * media, gpointer user_data) { GstElement *element, *appsrc, *appsink; GstCaps *caps; MyContext *ctx; ctx = g_new0 (MyContext, 1); gchar* pipeline_description = g_strdup_printf("filesrc name=vin location=%s ! qtdemux ! h264parse ! appsink name=vid max-buffers=3 drop=false",(char*)user_data); /* This pipeline generates H264 video. The appsinks are kept small so that if delivery is slow, * encoded buffers are dropped as needed.*/ ctx->generator_pipe = gst_parse_launch(pipeline_description,NULL); /* make sure the data is freed when the media is gone */ g_object_set_data_full (G_OBJECT (media), "rtsp-extra-data", ctx,(GDestroyNotify) ctx_free); /* get the element (bin) used for providing the streams of the media */ element = gst_rtsp_media_get_element (media); /* Find the app source video, and configure it, connect to the * signals to request data */ /* configure the caps of the video */ // TODO identify the caps from the stream caps = gst_caps_new_simple ("video/x-h264", "stream-format", G_TYPE_STRING, "byte-stream", "alignment", G_TYPE_STRING, "au", "width", G_TYPE_INT, 2880, "height", G_TYPE_INT, 1860, "framerate", GST_TYPE_FRACTION, 30, 1, NULL); ctx->vid_appsrc = appsrc = gst_bin_get_by_name_recurse_up (GST_BIN (element), "videosrc"); ctx->vid_appsink = appsink = gst_bin_get_by_name (GST_BIN (ctx->generator_pipe), "vid"); gst_util_set_object_arg (G_OBJECT (appsrc), "format", "time"); g_object_set (G_OBJECT (appsrc), "caps", caps, NULL); g_object_set (G_OBJECT (appsink), "caps", caps, NULL); /* install the callback that will be called when a buffer is needed */ g_signal_connect (appsrc, "need-data", (GCallback) need_data, ctx); gst_caps_unref (caps); GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (ctx->generator_pipe)); gst_bus_add_watch (bus, bus_callback, ctx->generator_pipe); gst_object_unref (bus); gst_element_set_state (ctx->generator_pipe, GST_STATE_PLAYING); gst_object_unref (element); } int main (int argc, char *argv[]) { GMainLoop *loop; GstRTSPServer *server; GstRTSPMountPoints *mounts; GstRTSPMediaFactory *factory; if(argc < 2) { g_print("The video filename is missing\n"); g_print("%s <filename>\n",argv[0]); return 1; } gst_init (&argc, &argv); loop = g_main_loop_new (NULL, FALSE); /* create a server instance */ server = gst_rtsp_server_new (); /* get the mount points for this server, every server has a default object * that be used to map uri mount points to media factories */ mounts = gst_rtsp_server_get_mount_points (server); /* make a media factory for a test stream. The default media factory can use * gst-launch syntax to create pipelines. * any launch line works as long as it contains elements named pay%d. Each * element with pay%d names will be a stream */ factory = gst_rtsp_media_factory_new (); gst_rtsp_media_factory_set_launch (factory, "( appsrc name=videosrc ! h264parse ! rtph264pay name=pay0 pt=96 )"); gst_rtsp_media_factory_set_shared(factory, TRUE); /* notify when our media is ready, This is called whenever someone asks for * the media and a new pipeline with our appsrc is created */ g_signal_connect (factory, "media-configure", (GCallback) media_configure, argv[1]); /* attach the test factory to the /test url */ gst_rtsp_mount_points_add_factory (mounts, "/test", factory); /* don't need the ref to the mounts anymore */ g_object_unref (mounts); /* attach the server to the default maincontext */ gst_rtsp_server_attach (server, NULL); /* start serving */ g_print ("stream ready at rtsp://127.0.0.1:8554/test\n"); g_main_loop_run (loop); return 0; }
辅助信息
- 运行
GST_DEBUG=4可查看详细应用日志 - 运行环境:Ubuntu 20.04,Gstreamer版本1.16.3
问题分析与解决方案
核心问题点
管道状态切换未同步完成
调用gst_element_set_state(pipeline, GST_STATE_NULL)后仅休眠100ms就操作元素,此时管道可能还未完全进入NULL状态,属性修改或seek操作无法生效。GStreamer状态切换是异步的,必须等待状态变更完成才能继续操作。下游元素未重置
修改filesrc的location属性后,qtdemux等下游元素仍缓存着原文件的索引信息,仅重启管道状态无法让下游感知文件变化,导致新视频无法正确解析。Seek操作时机错误
注释的seek代码在管道未进入READY状态前执行,必然失败;且EOS后下游元素已处于终止状态,无法处理seek请求。
修复方案
方案一:重新创建生成管道(支持切换视频)
修改bus_callback,彻底销毁原管道后创建新管道,确保所有元素重新初始化:
gboolean bus_callback(GstBus *bus, GstMessage *msg, gpointer data) { MyContext *ctx = (MyContext *)data; switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: g_print("GST_MESSAGE_EOS: %s\n", gst_message_type_get_name(GST_MESSAGE_TYPE(msg))); // 销毁原管道 gst_element_set_state(ctx->generator_pipe, GST_STATE_NULL); gst_object_unref(ctx->generator_pipe); // 创建新管道(切换到video02.mp4) gchar* pipeline_desc = g_strdup_printf("filesrc name=vin location=video02.mp4 ! qtdemux ! h264parse ! appsink name=vid max-buffers=3 drop=false"); ctx->generator_pipe = gst_parse_launch(pipeline_desc, NULL); g_free(pipeline_desc); // 重新配置appsink ctx->vid_appsink = gst_bin_get_by_name(GST_BIN(ctx->generator_pipe), "vid"); GstCaps *caps = gst_caps_new_simple ("video/x-h264", "stream-format", G_TYPE_STRING, "byte-stream", "alignment", G_TYPE_STRING, "au", "width", G_TYPE_INT, 2880, "height", G_TYPE_INT, 1860, "framerate", GST_TYPE_FRACTION, 30, 1, NULL); g_object_set(G_OBJECT(ctx->vid_appsink), "caps", caps, NULL); gst_caps_unref(caps); // 重新绑定总线监听 GstBus *new_bus = gst_pipeline_get_bus(GST_PIPELINE(ctx->generator_pipe)); gst_bus_add_watch(new_bus, bus_callback, ctx); gst_object_unref(new_bus); // 启动新管道 gst_element_set_state(ctx->generator_pipe, GST_STATE_PLAYING); break; default: g_print("got message %s\n", gst_message_type_get_name(GST_MESSAGE_TYPE(msg))); break; } return TRUE; }
同时修改media_configure中的总线监听参数,传递ctx而非ctx->generator_pipe:
gst_bus_add_watch(bus, bus_callback, ctx);
方案二:正确Seek实现循环播放(同一视频)
若仅需循环同一视频,可在EOS后等待管道进入READY状态再执行seek:
gboolean bus_callback(GstBus *bus, GstMessage *msg, gpointer data) { GstElement *pipeline = GST_ELEMENT(data); switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: g_print("GST_MESSAGE_EOS: %s\n", gst_message_type_get_name(GST_MESSAGE_TYPE(msg))); // 切换到READY状态 gst_element_set_state(pipeline, GST_STATE_READY); // 等待状态切换完成(可通过STATE_CHANGED消息精确监听,此处简化为休眠) g_usleep(500000); // 执行seek到起始位置 if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { g_print("Seek failed!\n"); } // 重启播放 gst_element_set_state(pipeline, GST_STATE_PLAYING); break; default: g_print("got message %s\n", gst_message_type_get_name(GST_MESSAGE_TYPE(msg))); break; } return TRUE; }
额外注意事项
- 避免在GStreamer主线程中使用
std::this_thread::sleep_for,建议用g_timeout_add或监听STATE_CHANGED消息替代休眠,防止阻塞事件循环。 - 重新创建管道时需确保所有旧元素被正确
unref,避免内存泄漏。 - 你的
need_data函数中已将PTS/DTS转换为运行时间(从0开始),这能避免客户端出现时间戳异常,需保留该逻辑。
内容的提问来源于stack exchange,提问作者graugans
相关产品推荐
相关产品推荐

