在播放复杂媒体时,每个声音和视频样本必须在特定时间以特定顺序播放。为此,GStreamer 提供了一种同步机制。
GStreamer为以下用例提供支持:
- 访问速度快于播放速度的非实时源。这是从文件中读取媒体并以同步方式播放的情况。在这种情况下,需要同步多个流,如音频、视频和字幕。
-
从多个实时源捕获和同步混合/混合媒体。这是一个典型的用例,您从麦克风/摄像头录制音频和视频并将其复用到文件中进行存储。
-
使用缓冲从(慢速)网络流进行流式传输。这是典型的 Web 流媒体案例,您使用 HTTP 从流媒体服务器访问内容。
-
从实时源中捕获并以可配置的延迟播放。例如,当从相机捕获、应用效果并显示结果时,会使用此方法。当使用 UDP 通过网络流式传输低延迟内容时,也会使用它。
-
从预先录制的内容中同时实时捕获和回放。这用于播放以前录制的音频并录制新样本的音频录制情况,目的是使新音频与以前录制的数据完美同步。
GStreamer 使用一个GstClock
对象、缓冲区时间戳和一个SEGMENT
事件来同步管道中的流(后后面文章会提到)。
时钟运行时间
在典型的计算机中,有很多来源可以用作时间源,例如系统时间、声卡、CPU 性能计数器等。因此,GStreamer 有很多GstClock
可用的实现。请注意,时钟时间不必从 0 或任何其他已知值开始。一些时钟从特定的开始日期开始计数,其他时钟从上次重新启动等开始。
运行时间是指之前绝对时间的快照(称为基础时间)和其他绝对时间之间的差异。
runing-time = absolute-time - base-time
GStreamer GstPipeline对象在进入PLAYING状态时维护一个GstClock对象和一个基时间。该管道为管道中的每个元素提供一个选择的GstClock句柄,以及选择的base-time。管道将以这样一种方式选择基本时间,即运行时间反映在PLAYING状态中花费的总时间。因此,当管道被暂停时,运行时间就会停止。
由于管道中的所有对象都具有相同的时钟和基时间,因此它们都可以根据管道时钟计算运行时间。
Buffer运行时间
为了计算缓冲区的运行时间,我们需要一个缓冲区时间戳和在缓冲区之前的SEGMENT事件。首先,我们可以将SEGMENT事件转换为GstSegment对象,然后使用gst_segment_to_running_time()函数来执行缓冲区运行时间的计算。
现在,同步就是确保在时钟到达相同的运行时间时播放具有特定运行时间的缓冲区。通常,此任务由汇聚元素执行。这些元素还必须考虑配置的管道的延迟,并在与管道时钟同步之前将其添加到缓冲区运行时。
非实时源时间戳缓冲区的运行时间从0开始。在一次刷新seek之后,它们将在运行时为0时再次产生缓冲区。
当缓冲区的第一个字节被捕获时,实时源需要带有运行时间的时间戳缓冲区 这个运行时间与管管道的运行时间相匹配。
Buffer stream-time
缓冲流时间,表示在流中的位置,这个位置所在的值范围是在0到媒体的总持续时间,它是从缓冲时间戳和之前的SEGMENT 事件计算得来。
stream-time 被用在:
- 使用POSITION查询报告流中的当前位置。
- 在seek事件和queries中使用的位置。
- 用于同步受控值的位置。
流时间从来不用于同步流,同步流只通过运行时间完成。
Time概述
这里是 用在GStreamer中的不同时间线的概述。
您可以看到缓冲区的运行时间总是随着时钟时间单调递增。当缓冲区的运行时间等于时钟-时间-基础-时间时,缓冲区被播放。流时间表示流中的位置,并在重复时向后跳转。
时钟的提供者(Clock providers)
时钟提供程序是管道中的一个元素,它可以提供GstClock对象。当元素处于PLAYING状态时,时钟对象需要报告一个单调递增的绝对时间。当元素被暂停时,允许暂停时钟。
时钟提供者的存在是因为它们以一定的速率播放媒体,而这个速率不一定与系统时钟速率相同。例如,一个声卡可以以44.1 kHz的频率播放,但这并不意味着根据系统时钟,在一秒之后,声卡就可以播放44100个样本。这只在近似值下成立。事实上,音频设备有一个内部时钟,基于我们可以曝光的样本播放数量。
如果一个具有内部时钟的元素需要同步,它需要根据管道时钟估计时间将根据内部时钟发生。为了估计这一点,它需要从它的时钟到管道时钟。
它需要估算什么时候管道时钟替换元素内部时钟。元素需要将自己的时钟从属于管道时钟。
如果管道时钟恰好是一个元素的内部时钟,则该元素可以跳过从步骤,直接使用管道时钟来调度回放。这样做更快也更准确。因此,通常带有内部时钟(如音频输入或输出设备)的元素将是管道的时钟提供程序。
当管道进入PLAYING状态时,它将遍历管道中从接收器到源的所有元素,并询问每个元素是否可以提供时钟。最后一个可以提供时钟的元素将用作管道中的时钟提供程序。该算法更喜欢典型回放管道中的音频接收器时钟和典型捕获管道中的源元素时钟。
有一些总线消息可以让您了解管道中的时钟和时钟提供程序。通过查看总线上的NEW_CLOCK消息,可以看到管道中选择了哪个时钟。当从管道中删除一个时钟提供程序时,会发布一个CLOCK_LOST消息,应用程序应该转到PAUSED,然后返回PLAYING以选择一个新的时钟。
延迟(Latency)
延迟是在时间戳X上捕获的样本到达接收器所需的时间。这个时间是与管道中的时钟相比较的。对于只与时钟同步的元素是接收器的管道,延迟总是0,因为没有其他元素在延迟缓冲区。
对于带有活动源的管道,会引入延迟,这主要是因为活动源的工作方式。考虑一个音频源,它将在时间0开始捕获第一个样本。如果源在44100Hz的时间推送带有44100个样本的缓冲区,它将在第2秒1收集缓冲区。由于缓冲区的时间戳是0,时钟的时间现在是>= 1秒,因此接收器将丢弃这个缓冲区,因为它太晚了。如果接收器中没有任何延迟补偿,所有缓冲区将被丢弃。
延迟补偿(Latency compensation)
在pipeline进入 PLAYING 状态之前,除了选择时钟和计算基准时间之外,它还将计算流水线中的延迟。它通过对管道中的所有接收器执行 LATENCY 查询来实现这一点。然后管道选择管道中的最大延迟并使用 LATENCY 事件对其进行配置。
动态延迟(Dynamic Latency)
向/从管道添加/删除元素或更改元素属性可以更改管道中的延迟.元素可以通过在总线上发布 LATENCY 消息来请求管道中的延迟更改。然后,应用程序可以决定是否查询和重新分配新的延迟。更改管道中的延迟可能会导致视觉或听觉故障,因此应仅在允许的情况下由应用程序完成。