当前位置: 首页 > article >正文

GStreamer 简明教程(九):Seek 与跳帧

系列文章目录

  • GStreamer 简明教程(一):环境搭建,运行 Basic Tutorial 1 Hello world!
  • GStreamer 简明教程(二):基本概念介绍,Element 和 Pipeline
  • GStreamer 简明教程(三):动态调整 Pipeline
  • GStreamer 简明教程(四):Seek 以及获取文件时长
  • GStreamer 简明教程(五):Pad 相关概念介绍,Pad Capabilities/Templates
  • GStreamer 简明教程(六):利用 Tee 复制流数据,巧用 Queue 实现多线程
  • GStreamer 简明教程(七):实现管道的动态数据流
  • GStreamer 简明教程(八):常用工具介绍

文章目录

  • 系列文章目录
  • 前言
  • Seek Events
  • Step Events
  • Show me the code
  • 参考


前言

本文对 Basic tutorial 13: Playback speed 内容进行说明,重点是理解 GStreamer 中 seek events 和 step events。

Seek Events

在看视频的过程中,用户拖动滑动条跳转到指定播放位置是基本需求,在 GStreamer 中我们可以像 pipeline 发送一个 seek event 来实现。

GStreamer 中创建 seek events 接口原型如下:

GstEvent *
gst_event_new_seek (gdouble rate, GstFormat format, GstSeekFlags flags,
    GstSeekType start_type, gint64 start, GstSeekType stop_type, gint64 stop);

让我详细解释各个参数:

  1. rate (gdouble):

    • 播放速率倍数
    • 1.0 表示正常速度
    • 2.0 表示双倍速度
    • 负值表示倒放
    • 0.5 表示半速播放
  2. format (GstFormat):

    • 指定查找的格式单位
    • 常用值包括:
      • GST_FORMAT_TIME: 时间格式(纳秒)
      • GST_FORMAT_BYTES: 字节格式
      • GST_FORMAT_DEFAULT: 默认格式(如对音频来说是采样数)
  3. flags (GstSeekFlags):

    • seek 操作的标志位组合
    • 常用标志:
      • GST_SEEK_FLAG_FLUSH: 清空管道中的数据
      • GST_SEEK_FLAG_ACCURATE: 精确定位
      • GST_SEEK_FLAG_KEY_UNIT: 定位到关键帧
      • GST_SEEK_FLAG_SEGMENT: 执行片段播放
  4. start_type (GstSeekType):

    • 定义如何解释 start 参数
    • 常用值:
      • GST_SEEK_TYPE_NONE: 忽略起始位置
      • GST_SEEK_TYPE_SET: 绝对位置
      • GST_SEEK_TYPE_CUR: 相对当前位置
  5. start (gint64):

    • 开始位置的值
    • 具体含义取决于 format 和 start_type
  6. stop_type (GstSeekType):

    • 定义如何解释 stop 参数
    • 与 start_type 使用相同的值
  7. stop (gint64):

    • 结束位置的值
    • 具体含义取决于 format 和 stop_type

使用示例:

// 跳转到视频的 2 秒位置
GstEvent *seek_event = gst_event_new_seek(
    1.0,                    // 正常播放速度
    GST_FORMAT_TIME,        // 使用时间格式
    GST_SEEK_FLAG_FLUSH,    // 清空管道
    GST_SEEK_TYPE_SET,      // 绝对位置
    2 * GST_SECOND,         // 开始位置:2秒
    GST_SEEK_TYPE_NONE,     // 不设置结束位置
    0                       // 结束位置(未使用)
);

在 GStreamer - Seeking 中对一些细节内容做了补充,大家有兴趣可以自己过一遍,我这里罗列几个我感兴趣的点:

  1. Seek 可以指定一个时间段进行播放,如果 flag 中不包含 GST_SEEK_FLAG_SEGMENT,那么片段播放结束后返回 GST_MESSAGE_EOS(“message::eos”),如果包含那么返回的是 GST_MESSAGE_SEGMENT_DONE(“mesaage::segment-done”)。我们可以监听 GST_MESSAGE_SEGMENT_DONE 消息,重新再发送一个 seek 时间以便循环播放某个片段。
  2. 对于一个 Pipeline ,我们向其 Sink 节点发送 seek 事件即可,seek 事件会通过管道向上游传播,直到到达源元素(source element)。你当然可以向一个 bin 发送 seek 事件,默认情况下它的所有 sink 节点都会收到 seek 事件。
  3. Trick mode flags 可以跳过一些帧,这在某些场景下是有用的,例如:
    • GST_SEEK_FLAG_TRICKMODE_KEY_UNITS: 只解码/显示关键帧
    • GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED: 跳过 B 帧
    • GST_SEEK_FLAG_TRICKMODE_NO_AUDIO: 不解码音频

Step Events

在某些场景我们需要精细控制回放,比如在视频编辑软件中进行逐帧查看,或者在某些诊断和测试应用中使用。这时候我们使用 step event 来控制精确的步进播放,即逐帧或逐块地处理媒体流。创建 step event 接口原型如下:

GstEvent *
gst_event_new_step (GstFormat format, guint64 amount, gdouble rate, gboolean flush, gboolean intermediate)

函数参数说明:

  1. GstFormat format: 指定步进的格式,常见的格式包括:

    • GST_FORMAT_TIME: 时间格式(纳秒)
    • GST_FORMAT_BUFFERS: 缓冲区数量
    • GST_FORMAT_DEFAULT: 默认格式(帧数)
  2. guint64 amount: 指定步进的数量(根据 format 参数解释)

    • 如果 format 是 GST_FORMAT_TIME,则表示纳秒
    • 如果 format 是 GST_FORMAT_BUFFERS,则表示缓冲区数量
  3. gdouble rate: 指定步进的速率

    • 1.0 表示正常速度
    • 1.0 表示快进

    • <1.0 表示慢放
  4. gboolean flush: 是否在步进前清空管道

    • TRUE: 清空管道中的数据
    • FALSE: 保留管道中的数据
  5. gboolean intermediate: 是否为中间步进

    • TRUE: 表示这是一系列步进中的一个
    • FALSE: 表示这是单独的步进

使用示例:

// 创建一个步进事件,向前移动 1 秒(1000000000 纳秒)
GstEvent *step_event = gst_event_new_step (
    GST_FORMAT_TIME,      // 使用时间格式
    1000000000,          // 1秒 (纳秒单位)
    1.0,                 // 正常速度
    TRUE,                // 清空管道
    FALSE                // 非中间步进
);

// 创建一个步进事件,向前移动 1 帧
GstEvent *step_event = gst_event_new_step (
    GST_FORMAT_BUFFERS,     // 使用时间格式
    1,          			// 1 帧
    1.0,                 	// 正常速度
    TRUE,                	// 清空管道
    FALSE                	// 非中间步进
);

主要用途:

  1. 实现帧步进功能(逐帧播放)
  2. 实现快进/跳跃播放,例如快进 5s
  3. 在视频编辑应用中进行精确定位
  4. 用于调试和分析媒体流

通常我们会在视频暂停的时候发送 step event 进行跳帧,另外注意到 step event 也有一个 rate 参数,实际体验下来这个 rate 并不是播放速度,而是计算步进的倍率。比如 amount = 1s ,rate = 2.0 时,发送这个 step event 后实际快进了 2s。

此外,step event 只会影响 sink 节点元素,而 seek event 则会影响整个 pipeline,每个元素都会对 seek event 做出反应,但 step event 速度更快。当你只对 video sink 节点进行快进后,你会发现音画发生了不同步,因为 audio sink 节点没有快进,因此可以同时对 video sink 和 audio sink 发送 step event 来避免音画不同步的问题。

Show me the code

本文的代码在 Basic tutorial 13: Playback speed 基础上做了一些修改和添加,主要是为了说明循环播放和跳转下一个 5s 要如何实现。详细代码参考 my_examples/basic-tutorial-13.c

这里对代码中重要部分进行说明

  /* Print usage map */
  g_print ("USAGE: Choose one of the following options, then press enter:\n"
      " 'P' to toggle between PAUSE and PLAY\n"
      " 'S' to increase playback speed, 's' to decrease playback speed\n"
      " 'k' to seek with segment [0, 10] and play looping\n"
      " 'D' to toggle playback direction\n"
      " 'N' to move to next frame (in the current direction, better in PAUSE)\n"
      " 'n' to move to 5s \n"
      " 'Q' to quit\n");

这里对程序功能进行说明,主要包含以下功能:

  1. ‘P’: 播放/暂停切换
  2. ‘S/s’: 调整播放速度(S增加,s减少)
  3. ‘k’: 在0-10秒区间循环播放
  4. ‘D’: 切换播放方向
  5. ‘N’: 下一帧(在当前方向,暂停状态下效果更好)
  6. ‘n’: 跳转5秒
  7. ‘Q’: 退出程序

注意,你需要在命令的执行窗口输入这些参数,而不是在播放窗口。

data.pipeline =
      gst_parse_launch
      ("playbin uri=file:///Users/user/Documents/work/测试视频/video_1280x720_30fps_180sec.mp4",
      NULL);

上述代码中构建了一个 pipeline,播放本地文件,文件路径根据你自己电脑上的情况进行修改即可。

g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);

这行代码是在设置键盘输入的监听器(事件处理),具体分解如下:

g_io_add_watch 是 GLib 库中的一个函数,用于添加对 I/O 事件的监听。它有三个主要参数:

  1. io_stdin:输入源(这里是标准输入,即键盘输入)
  2. G_IO_IN:表示监听输入事件
  3. handle_keyboard:回调函数,当有键盘输入时会调用这个函数
  4. &data:传递给回调函数的数据

当用户在键盘上输入内容时,handle_keyboard 函数会被触发,从而处理用户的输入命令。这是实现交互式命令行界面的关键部分,使程序能够响应用户的键盘输入。那么看看各个事件都在做什么。

static gboolean
handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
{
  gchar *str = NULL;

  if (g_io_channel_read_line (source, &str, NULL, NULL,
          NULL) != G_IO_STATUS_NORMAL) {
    return TRUE;
  }

  switch (g_ascii_tolower (str[0])) {
    case 'p':
      data->playing = !data->playing;
      gst_element_set_state (data->pipeline,
          data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
      g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
      break;
    case 's':
      if (g_ascii_isupper (str[0])) {
        data->rate *= 2.0;
      } else {
        data->rate /= 2.0;
      }
      send_seek_event (data);
      break;
  case 'k':
    send_segment_seek_event(data);
    break;
    case 'd':
      data->rate *= -1.0;
      send_seek_event (data);
      break;
    case 'n':
      if (data->video_sink == NULL) {
        /* If we have not done so, obtain the sink through which we will send the step events */
        g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
      }

      if(data->audio_sink == NULL) {
        g_object_get (data->pipeline, "audio-sink", &data->audio_sink, NULL);
      }

    if (g_ascii_isupper (str[0])) {
      gst_element_send_event (data->video_sink,
          gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE,
              FALSE));
      g_print ("Stepping one frame\n");
    }else {
      gst_element_send_event (data->video_sink,
          gst_event_new_step (GST_FORMAT_TIME, 5 * GST_SECOND, ABS (data->rate), TRUE,
              FALSE));
      gst_element_send_event (data->audio_sink,
          gst_event_new_step (GST_FORMAT_TIME, 5 * GST_SECOND, ABS (data->rate), TRUE,
              FALSE));
      g_print ("Stepping 5s\n");
    }

      break;
    case 'q':
      g_main_loop_quit (data->loop);
      break;
    default:
      break;
  }

这是一个键盘事件处理函数,针对不同的按键执行不同的操作:

  1. ‘p/P’:播放/暂停切换
  • 切换 data->playing 的状态
  • 通过 gst_element_set_state 设置播放器状态(PLAYING 或 PAUSED)
  1. ‘s/S’:调整播放速度
  • 大写’S’:速度翻倍(速率×2)
  • 小写’s’:速度减半(速率÷2)
  • 调用 send_seek_event 应用新的速率
  1. ‘k/K’:设置片段循环播放
  • 调用 send_segment_seek_event 进行片段循环播放
  1. ‘d/D’:改变播放方向
  • 将速率乘以-1(改变正负号)
  • 调用 send_seek_event 应用新的方向
  1. ‘n/N’:帧进/时间进
  • 首次使用时获取视频和音频接收器(sink)
  • 大写’N’:前进一帧(gst_event_new_step with GST_FORMAT_BUFFERS)
  • 小写’n’:前进5秒(gst_event_new_step with GST_FORMAT_TIME)
  1. ‘q/Q’:退出程序
  • 调用 g_main_loop_quit 退出主循环
GstBus *bus = gst_element_get_bus (data.pipeline);
  gst_bus_add_signal_watch(bus);
  g_signal_connect(G_OBJECT(bus), "message::segment-done", (GCallback)segment_done_callback, &data);

为了实现片段循环播放,我们这里监听 “message::segment-done” 信号,如果收到了信息,则调用 segment_done_callback

static void segment_done_callback(GstBus *bus, GstMessage *msg, CustomData *data) {
  g_print("Segment done, seeking again\n");
  // 重新执行 seek
  gst_element_seek(data->pipeline,
      1.0,
      GST_FORMAT_TIME,
      GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SEGMENT,
      GST_SEEK_TYPE_SET,
      0 * GST_SECOND,
      GST_SEEK_TYPE_SET,
      5 * GST_SECOND);
  data->segment_loop_count++;
  g_print("segment loop count:%d\n", data->segment_loop_count);
}

segment_done_callback 函数中,我们重新发送一个 seek event,范围是 [0, 5s],并且仍然带着 GST_SEEK_FLAG_SEGMENT,如此一来当播放片段结束后, GStreamer 会发送一个 “message::segment-done” 信号,然后又会触发回调函数,如此一直循环。

其他代码大家都比较熟悉了,这里就不再赘述了。

参考

  • Basic tutorial 13: Playback speed
  • GStreamer - Seeking
  • my_examples/basic-tutorial-13.c

http://www.kler.cn/a/398066.html

相关文章:

  • ODC 如何精确呈现SQL耗时 | OceanBase 开发者工具解析
  • 31.3 XOR压缩和相关的prometheus源码解读
  • 常见的测试方法
  • 【Android、IOS、Flutter、鸿蒙、ReactNative 】静态数组
  • 树状数组+概率论,ABC380G - Another Shuffle Window
  • kafka消费者出现频繁Rebalance
  • 基于Spring Boot的船运物流管理系统的设计与实现,LW+源码+讲解
  • shell编程--传参与数学运算
  • 开源模型应用落地-qwen模型小试-Qwen2.5-7B-Instruct-tool usage入门-串行调用多个tools(三)
  • HTTP 协议及内外网划分详解
  • pdf的统计图表数据提取;图表转excel
  • 大模型(LLMs)进阶篇
  • 环境贴图选用方式
  • 【MyBatis源码】深入分析TypeHandler原理和源码
  • python实现十进制转换二进制,tkinter界面
  • C++ 数组与结构 编程练习
  • vscode报错:Connecting with SSH time-out.
  • [vulnhub] Chronos: 1
  • linux系统kkFileView 配置https预览文件
  • [Docker#10] network | 架构 | CRUD | 5种常见网络类型 (实验)
  • Git主干分支master开发优缺点
  • 从入门到精通:一文掌握 Dockerfile 的用法!(多阶段构建与缓存优化)
  • 基于STM32的智能停车管理系统设计
  • 基于Java的医院病历管理系统
  • 人工智能引发直播革命:AI 技术塑造无人直播全新体验
  • 【python】用 Scrapy 实现高效爬虫项目