系列文章目录
- GStreamer 简明教程(一):环境搭建,运行 Basic Tutorial 1 Hello world!
- GStreamer 简明教程(二):基本概念介绍,Element 和 Pipeline
- GStreamer 简明教程(三):动态调整 Pipeline
文章目录
前言
本文内容比较简单,主要掌握以下两个知识点:
- 如何查询 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);
}
}
}
//...
}
在这段代码中,主要做了以下几件事情:
- 首先,通过获取新的状态来更新
data->playing
变量,如果新状态是GST_STATE_PLAYING
,则将data->playing
设置为TRUE
,反之为FALSE
。 - 接着,在新状态为
GST_STATE_PLAYING
时,创建一个查询query
以检索播放器元件(data->playbin
)的寻址信息。这里使用了gst_query_new_seeking
函数来创建一个寻址查询,并指定寻址格式为时间单位(GST_FORMAT_TIME
)。 - 然后,通过调用
gst_element_query
来执行查询,如果查询成功,解析查询结果,获取寻址是否启用(data->seek_enabled
)、起始位置(start
)和结束位置(end
)等信息。 - 根据获取到的信息,如果寻址已经启用(
data->seek_enabled
为TRUE
),则通过g_print
函数输出打印信息显示寻址已启用,并显示起始位置和结束位置的时间戳。 - 如果寻址未启用,则输出相应的提示信息表明此流媒体不支持寻址。
- 最后,在所有操作完成后,释放查询对象
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, ¤t)) {
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;
}
}
}
-
检查播放状态
if (data.playing)
:首先检查data.playing
标志是否为 TRUE,用以确保当前媒体处于播放状态。
-
查询当前播放位置
gint64 current = -1;
初始化一个变量current
用于存储当前播放位置。gst_element_query_position(data.playbin, GST_FORMAT_TIME, ¤t)
查询playbin
元素的当前播放位置,以时间格式(纳秒)返回。- 如果查询失败,输出错误信息:
Could not query current position.
。
-
查询流的时长(如果尚未获取)
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.
。
-
打印当前播放位置和总时长
g_print("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\n", GST_TIME_ARGS(current), GST_TIME_ARGS(data.duration));
- 使用宏
GST_TIME_FORMAT
和GST_TIME_ARGS
打印当前播放位置和总时长,以人类可读的格式显示。
-
条件满足时进行 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
)。
- Seek 功能已启用 (
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 行为,根据具体需求优化媒体播放体验。