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

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操作触发的状态变化或异步完成消息。

修复步骤

  1. 修正Seek完成的判断逻辑:Seek操作会触发管道重新进入PAUSED状态(因为Seek带了GST_SEEK_FLAG_FLUSH),应该在收到GST_MESSAGE_ASYNC_DONE消息或确认管道回到PAUSED状态后,再标记Seek完成并准备切换到PLAYING。
  2. 正确处理异步状态切换:调用gst_element_set_state(GST_STATE_PLAYING)后,不要立即判断结果,而是等待总线上的GST_STATE_CHANGED消息确认管道进入PLAYING状态。
  3. 调整状态机逻辑:优化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:54:57