GStreamer文件转UDP组播管道Seek后无法切换至播放状态
问题描述
我需要用GStreamer把视频文件推送到同一组播IP的多个端口。在Linux终端运行不带Seek操作的管道时一切正常,但现在要求启动时向前跳转几秒。我按照以下步骤操作:
- 初始将管道设置为PAUSED状态;
- 查询管道,在支持Seek时执行跳转操作;
- 成功Seek后将管道切换至PLAYING状态。
通过终端日志追踪,前两步已成功完成,但切换至PLAYING状态时返回GST_STATE_CHANGE_ASYNC,管道无法正常播放。相关代码如下:
//gst_player.cpp #include <gst/gst.h> #include <string> #include <iostream> typedef enum { STATUS_DEFAULT, STATUS_PAUSED, STATUS_READY_TO_SEEK, STATUS_SEEKED, STATUS_READY_TO_PLAY, STATUS_PLAYING, }MY_STATUS; typedef struct CfgStream_t { std::string src; std::string ip_sink; std::string port_sink; GstElement *playbin; MY_STATUS status; gboolean terminate; gboolean seek_enabled; gboolean seek_done; gint64 duration; } CfgStream_t; static void handle_message (CfgStream_t *data, GstMessage *msg); /* * ./gst_player /home/mta/media.mp4 226.1.1.110 35000 * */ int main(int argc, char *argv[]) { CfgStream_t data; GstBus *bus; GstMessage *msg; GstStateChangeReturn ret; GError* err = NULL; data.terminate = FALSE; data.seek_enabled = FALSE; data.seek_done = FALSE; data.duration = GST_CLOCK_TIME_NONE; data.status = STATUS_DEFAULT; if(argc >= 3) { /* Initialize GStreamer */ gst_init (NULL, NULL); try { data.src = std::string(argv[1]); data.ip_sink = std::string(argv[2]); data.port_sink = std::string(argv[3]); } catch(std::exception const &e) { g_printerr ("Unexpected source or sink information received.\n"); } std::string str_pipeline = "filesrc location=" + data.src + " ! qtdemux name=demux demux.video_0 ! queue ! h264parse disable-passthrough=true ! decodebin ! omxh264enc insert-sps-pps=true bitrate=1000000 ! rtph264pay pt=96 ! udpsink host=" + data.ip_sink + " auto-multicast=true port=" + data.port_sink; data.playbin = gst_parse_launch(str_pipeline.c_str(),&err); if (!data.playbin) { g_printerr ("pipeline creation failed %s.\n",err->message); return -1; } ret = gst_element_set_state (data.playbin, GST_STATE_PAUSED); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr ("Unable to set the pipeline to the playing state.\n"); gst_object_unref (data.playbin); return -1; } /* Listen to the bus */ bus = gst_element_get_bus (data.playbin); do { msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND, GstMessageType(GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION | GST_MESSAGE_ASYNC_DONE)); /* Parse message */ if (msg != NULL) { handle_message (&data, msg); } else { if(data.status == STATUS_READY_TO_SEEK && !data.seek_done) { g_print ("\n ENTER to Seeking mode...\n"); if (!gst_element_seek (data.playbin, 1.0, GST_FORMAT_TIME, GstSeekFlags(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), GST_SEEK_TYPE_SET, 10 * GST_SECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { g_print("Seek failed!\n"); } data.seek_done = TRUE; } if(data.status == STATUS_READY_TO_PLAY) { ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr ("Unable to set the pipeline to the playing state.\n"); gst_object_unref (bus); gst_element_set_state (data.playbin, GST_STATE_NULL); gst_object_unref (data.playbin); return -1; } g_print ("\n ENTER to PLAYING mode... %d ...\n", ret); // GST_STATE_CHANGE_ASYNC is returned } } } while (!data.terminate); /* Free resources */ gst_object_unref (bus); gst_element_set_state (data.playbin, GST_STATE_NULL); gst_object_unref (data.playbin); } else { g_printerr ("Number of Input arguments should be 3.\n"); } return 0; } static void handle_message (CfgStream_t *data, GstMessage *msg) { GError *err; gchar *debug_info; switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_ERROR: gst_message_parse_error (msg, &err, &debug_info); g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); g_clear_error (&err); g_free (debug_info); data->terminate = TRUE; break; case GST_MESSAGE_EOS: g_print ("\nEnd-Of-Stream reached.\n"); data->terminate = TRUE; break; case GST_MESSAGE_DURATION: /* The duration has changed, mark the current one as invalid */ data->duration = GST_CLOCK_TIME_NONE; break; case GST_MESSAGE_STATE_CHANGED: { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) { g_print ("Pipeline state changed from %s to %s:\n", gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); if(new_state == GST_STATE_PAUSED) { if(data->status == STATUS_DEFAULT) { GstQuery *query; gint64 start, end; query = gst_query_new_seeking (GST_FORMAT_TIME); if (gst_element_query (data->playbin, query)) { gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end); if (data->seek_enabled) { g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (start), GST_TIME_ARGS (end)); data->status = STATUS_READY_TO_SEEK; } else { g_print ("Seeking is DISABLED for this stream.\n"); } } else { g_printerr ("Seeking query failed.\n"); } gst_query_unref (query); } if(data->status == STATUS_READY_TO_SEEK && data->seek_done) { g_print ("SEEK Complete and I am ready to Play.\n"); data->status = STATUS_READY_TO_PLAY; } } else if(new_state == GST_STATE_PLAYING){ data->status = STATUS_PLAYING; } } } break; case GST_MESSAGE_ASYNC_DONE: g_print ("\nI am in ASYNC mode. \n"); break; default: /* We should not reach here */ g_printerr ("Unexpected message received.\n"); break; } gst_message_unref (msg); }
解决方案
问题根源
GST_STATE_CHANGE_ASYNC是GStreamer的正常返回值,它表示状态切换是异步进行的,需要等待总线的GST_MESSAGE_STATE_CHANGED消息确认状态真正切换完成。你的代码在调用gst_element_set_state后直接认为状态切换完成,但实际上还需要等待异步操作结束。另外,Seek操作完成的判断逻辑也有问题,不能仅靠seek_done标记,需要等待Seek操作触发的状态变化或异步完成消息。
修复步骤
- 修正Seek完成的判断逻辑:Seek操作会触发管道重新进入PAUSED状态(因为Seek带了
GST_SEEK_FLAG_FLUSH),应该在收到GST_MESSAGE_ASYNC_DONE消息或确认管道回到PAUSED状态后,再标记Seek完成并准备切换到PLAYING。 - 正确处理异步状态切换:调用
gst_element_set_state(GST_STATE_PLAYING)后,不要立即判断结果,而是等待总线上的GST_STATE_CHANGED消息确认管道进入PLAYING状态。 - 调整状态机逻辑:优化
MY_STATUS的状态流转,确保每个状态的切换都基于明确的总线消息,而非定时器轮询中的标记判断。
修复后的关键代码修改
1. 调整handle_message中的Seek完成处理
case GST_MESSAGE_ASYNC_DONE: g_print("\nSeek 异步操作完成,准备进入播放状态.\n"); if (data->status == STATUS_SEEKED) { data->status = STATUS_READY_TO_PLAY; } break; case GST_MESSAGE_STATE_CHANGED: { GstState old_state, new_state, pending_state; gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); if (GST_MESSAGE_SRC(msg) == GST_OBJECT(data->playbin)) { g_print("Pipeline state changed from %s to %s:\n", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state)); if(new_state == GST_STATE_PAUSED) { if(data->status == STATUS_DEFAULT) { // 保留原有的Seek支持查询逻辑 GstQuery *query; gint64 start, end; query = gst_query_new_seeking (GST_FORMAT_TIME); if (gst_element_query (data->playbin, query)) { gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end); if (data->seek_enabled) { g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (start), GST_TIME_ARGS (end)); data->status = STATUS_READY_TO_SEEK; } else { g_print ("Seeking is DISABLED for this stream.\n"); } } else { g_printerr ("Seeking query failed.\n"); } gst_query_unref (query); } else if (data->status == STATUS_READY_TO_SEEK && data->seek_done) { g_print("Seek 操作完成,等待异步确认.\n"); data->status = STATUS_SEEKED; } } else if(new_state == GST_STATE_PLAYING){ data->status = STATUS_PLAYING; g_print("管道已成功进入播放状态.\n"); } } } break;
2. 调整主循环中的状态触发逻辑
在MY_STATUS中新增一个等待状态:
typedef enum { STATUS_DEFAULT, STATUS_PAUSED, STATUS_READY_TO_SEEK, STATUS_SEEKED, STATUS_READY_TO_PLAY, STATUS_PLAYING_PENDING, // 新增:等待PLAYING状态确认 STATUS_PLAYING, }MY_STATUS;
修改主循环逻辑:
do { msg = gst_bus_timed_pop_filtered(bus, 100 * GST_MSECOND, GstMessageType(GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION | GST_MESSAGE_ASYNC_DONE)); if (msg != NULL) { handle_message(&data, msg); } else { if(data.status == STATUS_READY_TO_SEEK && !data.seek_done) { g_print("\n执行Seek操作...\n"); if (!gst_element_seek(data.playbin, 1.0, GST_FORMAT_TIME, GstSeekFlags(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), GST_SEEK_TYPE_SET, 10 * GST_SECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { g_print("Seek失败!\n"); } data.seek_done = TRUE; } if(data.status == STATUS_READY_TO_PLAY) { g_print("\n尝试切换到PLAYING状态...\n"); ret = gst_element_set_state(data.playbin, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr("无法将管道设置为播放状态.\n"); gst_object_unref(bus); gst_element_set_state(data.playbin, GST_STATE_NULL); gst_object_unref(data.playbin); return -1; } data.status = STATUS_PLAYING_PENDING; } } } while (!data.terminate);
额外建议
- 避免在定时器轮询中处理关键状态切换,尽量通过总线消息驱动状态流转,这样更符合GStreamer的异步模型。
- 可以使用
gst_element_get_state来同步等待状态切换完成,但在异步场景下推荐通过总线消息处理,避免阻塞主线程。 - 对于多端口组播推送,可以考虑创建多个独立的管道实例,每个管道对应一个端口,这样Seek和播放控制可以独立进行。
内容的提问来源于stack exchange,提问作者MAB
相关产品推荐
相关产品推荐

