GStreamer 简明教程(四):Seek 以及获取文件时长

系列文章目录



前言

本文内容比较简单,主要掌握以下两个知识点:

  • 如何查询 Pipeline 的信息,例如时长,当前播放位置等
  • 如何进行 seek 操作

一、Basic tutorial 4: Time management

代码太长不贴了,具体参考 Basic tutorial 4: Time management

1.1 处理 Bus 消息的不同策略

在前面的章节中,我们在主函数线程中通过 gst_bus_timed_pop_filtered 来处理 bus 的消息:

msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
                                 GST_MESSAGE_STATE_CHANGED |
                                 GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

请注意,在这种情况下,我们传入的第二个参数是 GST_CLOCK_TIME_NONE,这意味着我们会一直等待直到接收到所需的消息。这种方式适用于简单的例子,但在需要主线程执行其他操作时会有一定的局限性。

而在本章中,我们增加了等待超时逻辑,如下所示:

msg = gst_bus_timed_pop_filtered(bus, 100 * GST_MSECOND,
                                 GST_MESSAGE_STATE_CHANGED |
                                 GST_MESSAGE_ERROR | GST_MESSAGE_EOS |
                                 GST_MESSAGE_DURATION);

100 * GST_MSECOND 表示等待最多 100 毫秒。如果在此时间内没有消息到达,gst_bus_timed_pop_filtered 将返回 NULL

由于存在超时的可能,处理消息的逻辑也相应进行了调整:

if (msg != NULL) {
    handle_message(&data, msg);
} else {
    // 处理超时逻辑,主线程可以执行其他操作
}

为何这样处理?

这是因为我们在主函数中还需要执行其他任务,例如进行 seek 操作。假设我们希望在当前播放时间戳大于 10 秒时,将播放位置 seek 到 0 秒。如果主线程总是在等待消息,那么就无法及时进行这些操作。因此,通过设置超时时间,我们能够在等待消息的同时,让主线程定期执行其他逻辑,以实现更好的控制和响应能力。

这种处理方式同样适用于其他需要在主线程中执行的定期任务,提供了更高的灵活性和响应性。

2.1 查询 seek 信息

有些数据源,例如直播流,它是不允许进行 seek 操作的。因此我们需要查询 pipeline 是否支持 seek。这部分代码在 handle_message 中,这个函数重点看对 GST_MESSAGE_STATE_CHANGED 类型的处理逻辑,其他处理分支与之前章节一致,不再赘述。

static void handle_message(CustomData *data, GstMessage *msg) {
  GError *err;
  gchar *debug_info;

  switch (GST_MESSAGE_TYPE(msg)) {
  //...
  case GST_MESSAGE_STATE_CHANGED:{
      //...
      data->playing = (new_state == GST_STATE_PLAYING);
      
      if(data->playing){
        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));
          } else {
            g_print ("Seeking is DISABLED for this stream.\n");
          }
        }else {
          g_printerr ("Seeking query failed.");
        }

        gst_query_unref(query);
      }
    }
  }
  //...
}

在这段代码中,主要做了以下几件事情:

  1. 首先,通过获取新的状态来更新data->playing变量,如果新状态是GST_STATE_PLAYING,则将data->playing设置为TRUE,反之为FALSE
  2. 接着,在新状态为GST_STATE_PLAYING时,创建一个查询query以检索播放器元件(data->playbin)的寻址信息。这里使用了gst_query_new_seeking函数来创建一个寻址查询,并指定寻址格式为时间单位(GST_FORMAT_TIME)。
  3. 然后,通过调用gst_element_query来执行查询,如果查询成功,解析查询结果,获取寻址是否启用(data->seek_enabled)、起始位置(start)和结束位置(end)等信息。
  4. 根据获取到的信息,如果寻址已经启用(data->seek_enabledTRUE),则通过g_print函数输出打印信息显示寻址已启用,并显示起始位置和结束位置的时间戳。
  5. 如果寻址未启用,则输出相应的提示信息表明此流媒体不支持寻址。
  6. 最后,在所有操作完成后,释放查询对象query的引用计数,避免内存泄漏。

2.2 打印播放进度,进行 Seek

如果 gst_bus_timed_pop_filtered 等待超时,那我们打印下当前播放进度,并且尝试进行 seek

if (msg != NULL) {
  handle_message(&data, msg);
} else {
  /* We got no message, this means the timeout expired */
  if (data.playing) {
    gint64 current = -1;
    /* Query the current position of the stream */
    if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME, &current)) {
      g_printerr ("Could not query current position.\n");
    }
    /* If we didn't know it yet, query the stream duration */
    if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
      if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME, &data.duration)) {
        g_printerr ("Could not query current duration.\n");
      }
    }
    /* Print current position and total duration */
    g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\n",
            GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));
    /* If seeking is enabled, we have not done it yet, and the time is right, seek */
    if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {
      g_print ("\nReached 10s, performing seek...\n");
      gst_element_seek_simple (data.playbin, GST_FORMAT_TIME,
                              GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
      data.seek_done = TRUE;
    }
  }
}
  1. 检查播放状态

    • if (data.playing):首先检查 data.playing 标志是否为 TRUE,用以确保当前媒体处于播放状态。
  2. 查询当前播放位置

    • gint64 current = -1; 初始化一个变量 current 用于存储当前播放位置。
    • gst_element_query_position(data.playbin, GST_FORMAT_TIME, &current) 查询 playbin 元素的当前播放位置,以时间格式(纳秒)返回。
    • 如果查询失败,输出错误信息:Could not query current position.
  3. 查询流的时长(如果尚未获取)

    • if (!GST_CLOCK_TIME_IS_VALID(data.duration)):检查 data.duration 是否为有效的时间。如果初始状态或之前查询失败,此条件为真。
    • gst_element_query_duration(data.playbin, GST_FORMAT_TIME, &data.duration) 查询 playbin 元素的总时长,以时间格式(纳秒)返回。
    • 如果查询失败,输出错误信息:Could not query current duration.
  4. 打印当前播放位置和总时长

    • g_print("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\n", GST_TIME_ARGS(current), GST_TIME_ARGS(data.duration));
    • 使用宏 GST_TIME_FORMATGST_TIME_ARGS 打印当前播放位置和总时长,以人类可读的格式显示。
  5. 条件满足时进行 seek 操作

    • if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND):检查以下条件是否都满足:
      • Seek 功能已启用 (data.seek_enabled)。
      • 尚未进行过 seek 操作 (!data.seek_done)。
      • 当前播放时间超过 10 秒 (current > 10 * GST_SECOND)。
    • gst_element_seek_simple(data.playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
      • 调用 gst_element_seek_simple 执行简单的 seek 操作,将播放位置移动到 30 秒。
      • 使用 GST_SEEK_FLAG_FLUSH 丢弃管道中的数据,确保 seek 更加迅速。
      • 使用 GST_SEEK_FLAG_KEY_UNIT 确保 seek 到最接近的关键帧。
    • data.seek_done = TRUE; 标记已完成 seek 操作,防止重复执行。

2.3 Seek 标志位说明

好的,以下是对这些 GST_SEEK_FLAG 的详细解释:

GST_SEEK_FLAG_NONE

GST_SEEK_FLAG_NONE = 0
  • 描述: 表示不使用任何特别的 flag。
  • 应用场景: 当你不需要任何额外的 seek 行为时使用此标志。这是默认值。

GST_SEEK_FLAG_FLUSH

GST_SEEK_FLAG_FLUSH = (1 << 0)
  • 描述: 表示在执行 seek 操作之前,需要丢弃目前在管道中的所有数据。
  • 作用:
    • 增加管道的响应速度。
    • 使得 seek 操作立即生效,避免显示“过时”的数据。
  • 副作用: 可能导致短暂的暂停,因为需要时间重新填充管道中的数据。

GST_SEEK_FLAG_ACCURATE

GST_SEEK_FLAG_ACCURATE = (1 << 1)
  • 描述: 强制 seek 操作更加精确,确保到达指定的时间戳。
  • 作用:
    • 在某些媒体文件中,不提供足够的索引信息,导致寻址时间不准确。
    • 使用此标志会增加 seek 操作的准确性。
  • 副作用: 可能会增加 seek 操作的时间,因为需要更多的计算和处理。

GST_SEEK_FLAG_KEY_UNIT

GST_SEEK_FLAG_KEY_UNIT = (1 << 2)
  • 描述: 强制 seek 操作移到最近的关键帧(Key Frame)。
  • 作用:
    • 大多数视频编码格式只能准确地 seek 到关键帧位置。
    • 使用此标志会使 seek 操作立即显示数据,因为它跳到最近的关键帧。
  • 副作用: 可能导致 seek 操作到达的位置与实际请求的位置略有不同,但是保证了最快的响应时间。

应用场景示例

使用 GST_SEEK_FLAG_NONE
gst_element_seek_simple(playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, 30 * GST_SECOND);
  • 场景: 需要执行基本的 seek 操作,没有特别的需求。
使用 GST_SEEK_FLAG_FLUSH
gst_element_seek_simple(playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, 30 * GST_SECOND);
  • 场景: 需要快速响应的 seek 操作,愿意为此承担短暂的暂停。
使用 GST_SEEK_FLAG_ACCURATE
gst_element_seek_simple(playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_ACCURATE, 30 * GST_SECOND);
  • 场景: 需要精确的 seek 操作,不能容忍时间戳的误差,尽管可能耗时更长。
使用 GST_SEEK_FLAG_KEY_UNIT
gst_element_seek_simple(playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
  • 场景: 希望 seek 操作立即生效并开始显示数据,不介意位置略有偏差。

组合使用

gst_element_seek_simple(playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
  • 场景: 需要快速响应且显示数据的 seek 操作。

通过设置这些标志,开发者可以更灵活和高效地控制 GStreamer 的 seek 行为,根据具体需求优化媒体播放体验。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值