官网地址: https://gstreamer.freedesktop.org/documentation/application-development/advanced/clocks.html
Clocks and synchronization in GStreamer
当我们播放复杂的流媒体的时候,音频和视频一定要在明确的顺序和明确的时间下播放。为了达到这个目的,Gstreamer提供了一个同步机制。Gstreamer 支持以下使用情况: 1. 使用比回放速率更快的速度访问非直播源(Non-live sources)。我们从文件中读取媒体数据,然后使用同步的方式(synchronized fashion)进行播放。在这种情况下,多个流需要进行同步,比如音频、视频、字幕(subtitles)。
2. 从多个直播源采集和同步合并/混合的流媒体数据。这是一种典型的使用情况:你通过手机/相机将音视频录下来然后合并(mux)存储到一个文件夹。
3. 从(慢)网络流中使用缓冲来取流。这是一种典型的网页流情况,你可以通过http来访问流服务器的内容。
4. 从实时源中采集然后使用可配置延迟在实时源上回放。比如,从摄像机采集数据流,然后在摄像机上显示效果。这也可以用在使用UDP的方式在低延迟的网络上取流内容。
5. 同时进行采集和回放预录内容,当你播放先前录制的音频和录制一个新的样本数据,目的在于新录制的音频和先前录制的数据进行完美的同步。
Gstreamer 使用GstClock对象、buffer时间戳和SEGMENT事件来同步管线中的流。
Clock running-time
在一台电脑中,由于多源可以用作时钟源,比如:系统时间,声卡,CPU计数器。。。因此,在Gstreamer中有很多GstClock的实现方法。时钟时间不是总是从0或者某个值开始,一些时钟从已知的日期开始,
一些时钟从上一次重新启动开始,等等。。。
一些时钟从上一次重新启动开始,等等。。。
一个GstClock 通过gst_clock_get_time ()可以返回absolute-time的值。absolute-time (or clock time) 是单调递增的。通过absolute-time可以计算running-time的值:
running-time = absolute-time - base-time
当在播放状态的时候,一个GstPipeline对象可以维护一个GstClock 对象和base-time,管线可以将被选择的时候句柄和被选择的base-time送到每个组件当中。管线将会以running-time 反映在播放状态下总时间的方式来选择一个base-time,所以当暂停的时候,running-time也暂停。
因为所有的对象拥有同样的时钟和base-time,因此可以通过管线时钟来计算running-time。
running-time = absolute-time - base-time
当在播放状态的时候,一个GstPipeline对象可以维护一个GstClock 对象和base-time,管线可以将被选择的时候句柄和被选择的base-time送到每个组件当中。管线将会以running-time 反映在播放状态下总时间的方式来选择一个base-time,所以当暂停的时候,running-time也暂停。
因为所有的对象拥有同样的时钟和base-time,因此可以通过管线时钟来计算running-time。
Buffer running-time
为了计算一个buffer running-time, 我们需要一个buffer时间戳和SEGMENT事件。首先我们可以将SEGMENT事件转换成一个GstSegment对象,然后我们可以使用函数gst_segment_to_running_time () 来计算 buffer running-time。
同步机制就是要确保当时钟到达和buffer一样的running-time的时候buffer被播放。通常这个工作由sink 组件来完成。在同步时钟之前,sink 组件也需要把延迟考虑进去然后加到buffer running-time中。
非实时源时间戳buffer的running-time是从0开始的。在flushing seek之后,将会在running-time为0的时候重新产生buffers。
同步机制就是要确保当时钟到达和buffer一样的running-time的时候buffer被播放。通常这个工作由sink 组件来完成。在同步时钟之前,sink 组件也需要把延迟考虑进去然后加到buffer running-time中。
非实时源时间戳buffer的running-time是从0开始的。在flushing seek之后,将会在running-time为0的时候重新产生buffers。
Buffer stream-time
buffer的流时间,也就是流的位置,是用buffer时间戳和先前的SEGMENT时间计算出来的。它表示流媒体的时间,其值介于零和媒体文件总长度之间。
stream-time用在这几个地方使用:
1. 响应POSTION query,获取当前的位置。
2. seek事件中的position的设置。
3. the position used to synchronize controller values
stream-time用在这几个地方使用:
1. 响应POSTION query,获取当前的位置。
2. seek事件中的position的设置。
3. the position used to synchronize controller values
Time overview
这个是Gstreamer的时间线的总览。
下面的图片代表了在管线中的不同时间,播放100ms的样本然后重复50ms到100ms的部分。
下面的图片代表了在管线中的不同时间,播放100ms的样本然后重复50ms到100ms的部分。
你可以看到running-time随着clock-time单调递增,buffer在runningtime = clocktime - basetime的时候被播放,stream-time表示流的位置,当重复播放的时候会回跳。
Clock providers
时钟提供者是管线中的一个可以提供GstClock对象的组件。时钟对象在组件状态为PLAYING时需要报告一个单调递增的绝对时间,当组件状态为PAUSED时允许暂停时钟。
时钟提供者之所以存在是因为当以某一速率播放媒体时,并且这个速率不需要和系统时钟的速率一样。比如,一个声卡的以44.1HZ的频率播放,但是并不代表每秒钟播放44.1个样本。这只是一个估算值。实际上,音频设备有一个内部时钟,它基于我们可以发出来的样本数。
如果管线时钟是内部组件的时钟,组件可以跳过从属步骤直接使用管线时钟来安排回放。这将会更快也会更准确。因此,总体上来说,一个有内部时钟的组件比如输入或者输出设备将会是管线的时钟提供者。
当管线正在播放状态时,他将会从sink到source组件走遍,然后询问每个组件是否可以提供时钟最后一个可以提供的组件将会当做时钟提供者。这个算法更倾向于在回放管线中选择audio sink作为时钟,在采集管线中source组件作为时钟。
总线消息可以让你知道管线中的时钟和时钟提供者,你可以在NEW_CLOCK消息中查看哪个时钟被选择。当时钟提供者从管线中移除时,CLOCK_LOST消息会被传递,应用应该暂停然后播放重新选择一个新的时钟。
时钟提供者之所以存在是因为当以某一速率播放媒体时,并且这个速率不需要和系统时钟的速率一样。比如,一个声卡的以44.1HZ的频率播放,但是并不代表每秒钟播放44.1个样本。这只是一个估算值。实际上,音频设备有一个内部时钟,它基于我们可以发出来的样本数。
如果管线时钟是内部组件的时钟,组件可以跳过从属步骤直接使用管线时钟来安排回放。这将会更快也会更准确。因此,总体上来说,一个有内部时钟的组件比如输入或者输出设备将会是管线的时钟提供者。
当管线正在播放状态时,他将会从sink到source组件走遍,然后询问每个组件是否可以提供时钟最后一个可以提供的组件将会当做时钟提供者。这个算法更倾向于在回放管线中选择audio sink作为时钟,在采集管线中source组件作为时钟。
总线消息可以让你知道管线中的时钟和时钟提供者,你可以在NEW_CLOCK消息中查看哪个时钟被选择。当时钟提供者从管线中移除时,CLOCK_LOST消息会被传递,应用应该暂停然后播放重新选择一个新的时钟。
Latency
延时是指从采集到sink之间所花费的时间。管道中元素与时钟的同步仅仅发生在各个sink中,如果其他元素对buffer没有延迟的话,那么延迟就为0。延迟的引入主要是基于这样的考 虑,buffer从source推送到sink会花费一定的时间,从而可能导致buffer被丢弃。
延迟补偿
在管线转到PALYING状态之前,除了选择一个时钟和计算base-time之外,还会计算延迟时间。在所有的sink中通过一个LATENCY查询,管线将会选择最大的延迟然后使用一个LATENCY事件进行配置。
通过LATENCY事件的值所有的sink组件将会延迟回放,因为所有的sink都延迟相同时间,它们将会相对的同步。
动态延迟
从管线中 增加/移除 组件或者改变组件的属性将会改变管线中的延迟时间。一个组件可以通过在总线上发送一个LATENCY消息来请求一个延时的改变。应用程序可以决定是否进行查询和重新分配一个新的延迟。改变延迟时间可能会造成视觉上或听觉上的抖动,因此只有在被允许的情况下才能使用。
通过LATENCY事件的值所有的sink组件将会延迟回放,因为所有的sink都延迟相同时间,它们将会相对的同步。
动态延迟
从管线中 增加/移除 组件或者改变组件的属性将会改变管线中的延迟时间。一个组件可以通过在总线上发送一个LATENCY消息来请求一个延时的改变。应用程序可以决定是否进行查询和重新分配一个新的延迟。改变延迟时间可能会造成视觉上或听觉上的抖动,因此只有在被允许的情况下才能使用。
——20170630