文章

gstreamer插件编写指南:事件:搜索、导航及更多

事件类型多种多样,但在流水线中的传播方式却只有两种:下游或上游。了解这两种方式的工作原理非常重要,因为如果流水线中的某个元素没有正确处理这些事件,管道的整个事件系统就会崩溃。在此,我们将尝试解释这些方法的工作原理,以及元素应如何实现这些方法。

下游事件

下游事件通过汇衬底的事件处理函数接收,该处理函数在创建 pad 时使用gst_pad_set_event_function ()设置。

下游事件有两种传播方式:带内(与缓冲区流一起序列化)或带外(即时通过流水线,可能与处理缓冲区的流媒体线程不在同一线程,跳过流水线中正在处理或排队的缓冲区)。最常见的下游事件(SEGMENT、CAPS、TAG、EOS)都与缓冲流一起序列化。

下面是一个典型的事件功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static gboolean
gst_my_filter_sink_event (GstPad  *pad, GstObject * parent, GstEvent * event)
{
  GstMyFilter *filter;
  gboolean ret;

  filter = GST_MY_FILTER (parent);
  ...

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEGMENT:
      /* maybe save and/or update the current segment (e.g. for output
       * clipping) or convert the event into one in a different format
       * (e.g. BYTES to TIME) or drop it and set a flag to send a segment
       * event in a different format later */
      ret = gst_pad_push_event (filter->src_pad, event);
      break;
    case GST_EVENT_EOS:
      /* end-of-stream, we should close down all stream leftovers here */
      gst_my_filter_stop_processing (filter);
      ret = gst_pad_push_event (filter->src_pad, event);
      break;
    case GST_EVENT_FLUSH_STOP:
      gst_my_filter_clear_temporary_buffers (filter);
      ret = gst_pad_push_event (filter->src_pad, event);
      break;
    default:
      ret = gst_pad_event_default (pad, parent, event);
      break;
  }

  ...
  return ret;
}

如果你的元素是基于链的,那么你几乎总是需要实现一个 sink 事件函数,因为这就是通知你 SEGMENT、CAPS和流结束的方式。

如果你的元素完全基于循环,你可能需要也可能不需要汇入事件函数(因为元素正在驱动流水线,它会提前知道流的长度,或者通过gst_pad_pull_range()的流返回值得到通知)。在某些情况下,即使是基于循环的元素也可能会接收到来自上游的事件(例如前面带有 id3demux 或 apedemux 元素的音频解码器,或从在自定义事件中发送有关流的附加信息的源输入的解流器,如 DVD 源)。

上游事件

上游事件由管道下游的某个元素生成(例如:视频汇可能会生成搜索事件,向上游元素通报鼠标指针的当前位置)。这也可能是应应用程序的要求间接发生的,例如,当应用程序在流水线上执行搜索操作时,该搜索请求将被传递给汇元素,而汇元素又会生成一个上游搜索事件。

最常见的上游事件是搜索事件、服务质量(QoS)和重新配置事件。

上游事件可以使用gst_pad_send_event函数发送,该函数只需调用该 pad 的默认事件处理程序。pad 的默认事件处理器是gst_pad_event_default,它基本上是将事件发送给内部链接 pad 的对应设备。因此,上游事件总是到达元素的源 pad,并由默认事件处理程序处理,除非你重载该处理程序自行处理。在某些特殊情况下,你必须这样做:

  • 如果您的元素中有多个汇衬底。在这种情况下,您必须决定将事件发送到哪个汇衬底(如果不是全部的话)。

  • 如果您需要在本地处理该事件。例如,您需要在向上游发送之前转换的搜索事件,或者您需要处理的 QoS 事件。

在该事件处理程序中进行的处理并不重要,但有一些重要的规则必须绝对遵守,因为一个坏掉的元素事件处理程序就会破坏整个管道事件处理。这些规则如下

  • 始终使用默认的gst_pad_event_default方法处理你不会处理的事件。该方法会根据事件的不同,转发或删除事件。

  • 如果要根据收到的事件生成新事件,请不要忘记 gst_event_unref 收到的事件。

  • 事件处理函数应该返回 TRUE 或 FALSE,表示事件是否已被处理。除非您确实知道已经处理了该事件,否则切勿在处理程序中简单地返回 TRUE/FALSE。

  • 请记住,事件处理程序可能会从与媒体流线程不同的线程调用,因此请确保在任何地方都使用了适当的锁定。

事件汇总

本章列出了当前使用的所有已定义事件,以及如何使用/解释这些事件。您可以使用 GST_EVENT_TYPE 宏查看某个事件的类型(如果您需要一个字符串用于调试,也可以使用 GST_EVENT_TYPE_NAME)。

在本章中,我们将讨论以下事件:

  • 流启动

  • Caps

  • Segment

  • 标签(元数据)

  • 数据流结束(EOS)

  • 目录

  • Gap

  • 刷新开始

  • 刷新停止

  • 服务质量(QOS)

  • 请求搜索

  • 导航

有关事件以及如何在各种情况下正确使用事件的更全面信息,请查阅 GStreamer 设计文档。本节仅作概括介绍。

流启动

待添加……

Caps

CAPS 事件包含接下来的缓冲区的格式描述。有关协商的更多信息,请参阅能力集协商。

Segment

向下游发送段事件是为了宣布数据流中有效时间戳的范围,以及如何将其转换为运行时间和数据流时间。段事件必须始终在第一个数据缓冲区之前和刷新之后发送(见上文)。

第一个段事件由驱动流水线的元素创建,例如以推模式运行的信号源或以拉模式运行的解复用器/解码器。然后,段事件沿着流水线向下传输,途中可能会发生转换(例如,解码器可能会接收 BYTES 格式的段事件,并根据平均比特率将其转换为 TIMES 格式的段事件)。

根据元素类型的不同,可以使用gst_pad_event_default () 简单地转发事件,或者对事件进行解析并发送修改后的事件。最后一种情况适用于通常具有字节到时间转换概念的解复用器。它们的输入通常以字节为单位,因此传入的事件也会有一个以字节为单位的偏移量(GST_FORMAT_BYTES)。然而,下游元件则希望以时间单位来分段事件,以便与流水线时钟同步。因此,解复用器和类似元素不应转发事件,而应解析、释放事件并向下游发送段事件(以时间单位GST_FORMAT_TIME)。

段事件使用函数gst_event_new_segment () 创建。有关其参数的详细信息,请参阅 API 参考和设计文档。

解析该事件的元素可以使用gst_event_parse_segment()提取事件细节。元素可能会发现 GstSegment API 对跟踪当前段非常有用(例如,如果它们想将其用于对输出进行剪切)。

标签(元数据)

标签事件用于向下游发送,以显示从流数据中解析出的标签。目前,在将数据流从一种格式转码为另一种格式的过程中,这种方法可用于保留标记。标签在“标签(元数据和流信息)”中有大量讨论。大多数元素都会通过调用gst_pad_event_default () 来转发事件。

标签事件是通过函数gst_event_new_tag () 创建的,但更常见的情况是,元素会向下游发送一个标签事件,该事件会被汇元素转换成总线上的信息。所有这些函数都需要一个填写完整的标签列表作为参数,它们将拥有该标签列表的所有权。

解析该事件的元素可以使用函数gst_event_parse_tag ()获取事件包含的标记列表。

数据流结束(EOS)

如果元素发送的数据流结束,就会发送数据流结束事件。接收到该事件的元素(来自上游,因此会在其 sinkpad 上接收到)通常只会处理任何缓冲数据(如果有的话),然后将事件进一步转发到下游。gst_pad_event_default ()会处理所有这些工作,因此大多数元素都不需要支持该事件。明确需要关闭 EOS 资源的元素和 N 对 1 元素除外。请注意,流本身并不是一个应该在 EOS 时关闭的资源!应用程序可能会返回到 EOS 之前的某个点,然后继续播放。

EOS 事件没有属性,因此是 GStreamer 中最简单的事件之一。它通过gst_event_new_eos()函数创建。

需要注意的是,只有驱动流水线的元素才能发送 EOS 事件。如果您的元素是基于链的,那么它就不是在驱动流水线。基于链的元素只需在数据流(或配置的数据段)结束时从其链函数返回 GST_FLOW_EOS,然后驱动流水线的上游元素将负责发送 EOS 事件(或根据操作模式在总线上发布 SEGMENT_DONE 消息)。如果您要实现自己的源元素,也无需手动发送 EOS 事件,只需在创建或填充函数中返回 GST_FLOW_EOS(假设您的元素源自 GstBaseSrc 或 GstPushSrc)即可。

目录

待添加……

Gap

待添加……

刷新开始

如果流水线中的所有缓冲区和缓存都要清空,就会向下游(在推模式下)或上游(在拉模式下)发送 flush start 事件。例如,“队列”元素在收到该事件时会清空其内部缓冲区列表。文件汇元素(如 “filesink”)收到此事件后,将清空内核到磁盘的缓存(fdatasync()fflush ())。gst_pad_event_default ()就是这样做的,因此对于大多数元素来说,使用默认事件处理程序转发事件就足够了。

作为从流水线中刷新所有数据的副作用,该事件会使所有衬底拒绝数据,直到收到“刷新停止”信号(试图推送数据的元素会返回“FLUSHING_FLOW”并停止处理数据),从而解除媒体流线程的阻塞。

刷新启动事件是通过gst_event_new_flush_start () 创建的。与 EOS 事件一样,它没有任何属性。通常只有驱动流水线的元素才会创建该事件,如以推送模式运行的源元素或基于拉模式的解复用器/解码器。

刷新停止

刷新停止事件由驱动流水线的元素在刷新启动后发送,并告知下游的衬底和元素应再次接受事件和缓冲区(不过在缓冲区之前至少会有一个 SEGMENT 事件)。

如果元素保留了流数据的临时缓存,则应在接收到 FLUSH-STOP 事件时(以及在其链函数接收到设置了 DISCONT 标志的缓冲区时)清除这些缓存。

刷新停止事件是通过gst_event_new_flush_stop () 创建的。它有一个参数,用于控制流水线的运行时间是否应重置为 0。通常情况下,在刷新停止后,running_time 会被重置为 0。

服务质量(QOS)

QOS 事件包含有关流当前实时性能的报告。更多信息请参阅“服务质量 (QoS)”章节。

搜索请求

搜索事件用于向元素请求新的流位置。新位置支持多种格式设置(时间、字节或“默认单位”(表示视频帧、音频独立于信道的采样等))。寻址可以相对于文件结束或文件开始进行,通常发生在上游方向(下游搜索是通过发送 SEGMENT 事件完成的,该事件会为支持该功能的元素(如 filesink)提供适当的偏移量)。

接收搜索事件的元素应根据元素类型,或直接将其转发到上游(过滤器、解码器),或更改事件的格式然后转发(解耦器),或通过更改其内部流资源中的文件指针来处理事件(文件源、以拉模式驱动流水线的解复用器/解码器)或其他方式。

搜索事件是使用指定格式(时间、字节、单位)的位置建立的。它们通过函数gst_event_new_seek () 创建。请注意,许多插件不支持从流的末端开始搜索。未驱动流水线和转发搜索请求的元素不应假定搜索成功或实际发生,而应根据其接收到的 SEGMENT 事件进行操作。

解析该事件的元素可以使用gst_event_parse_seek()来完成解析。

导航

导航事件由视频汇向上游发送,以告知上游元素鼠标指针的位置、鼠标指针是否点击以及点击的位置,或者按键是否被按下或释放。

所有这些信息都包含在事件结构中,可通过gst_event_get_structure () 获取。

请查看 gst-plugins-good 中的 navigationtest 元素,了解如何从该事件中提取导航信息。

本文由作者按照 CC BY 4.0 进行授权