[GStreamer] 时钟 和 数据同步

GStreamer提供如下同步服务:

  • 非直播的音/视频播放,这种情况下,数据录入速度显著快于播放消耗的速度,这种情况下GStreamer会协调好音频、视频、字幕的同步情况。
  • 录像录音场景下,GStreamer会协调视频录制和音频录制的同步。
  • 流媒体场景下数据源的录入速度慢于播放速度,GStreamer会提供buffering服务。
  • 直播场景下,GStreamer提供一个可配置的延迟服务,这样可以保证直播的流畅,比如采集直播视频流并添加一些后期效果,再推送给播放端。
  • 音视频后期同步,比如实现录制好了一段视频,然后为视频做实时配音。

同步的三个重要对象:

GstClock,  buffer timerstamps,  SEGMENT event

Clock running-time:

GStreamer集成的时钟源有很多,比如cpu、声卡、系统时间等等,具体使用哪个可自行挑选。使用gst_clock_get_time ()可以获取一个基于当前时钟源的时间戳,每次调用函数这个值都会获取一个新的,且是逐渐增长的。

我们一般都会使用一个绝对时间作为基准时间,之后的所有时间都已此时间为基准。因此pipeline运行时间的计算就是 当前的 gst_clock_get_time () 返回值减去基准时间。

runningtime = absolutetime - basetime

absolutetime :任何时候调用gst_clock_get_time() 获得的时间。

basetime :pipeline被设置为PLAYING时候会自动调用gst_clock_get_time()获取一个时间,

                   然后存在pipeline结构体里。

GStreamer 用 GstClock 结构来维护时间对象。只有GstPipeline实例会维护一个GstClock实例同时pipeline还会把这个实例的指针导出来传递给所有自己的children element(GstElement结构体中有一个GstClock 指针)。

pipeline中的GstClock实例在pipeline的状态设置为PLAYING的时候才被创建,因此,在次之前如果children element试图强制访问自己的GstClock指针可能会导致异常。GstClock实例在创建的时候会立刻记录下当前的绝对时间作为基准时间。

当pipeline设置为PAUSED状态时,pipeline的runningtime会停止不再更新。

同一个pipeline中的所有element共享同一个GstClock实例,这个实例所有element都能访问,但是由pipeline实例管理生命周期。

Buffer running-time:

GStreamer里的Buffer(对应的结构为GstBuffer )是指一个内存单元,而不是缓冲区,每个GstBuffer 内部都有三个时间戳(pts,dts,duration)。所有的 Buffer都会在整个pipeline中传递,某些对时间敏感的 element 在处理 Buffer 的时候会以三个时间戳的值作为时间参考。具体的使用方法依据 element 的功能确定,比如 解码器就需要使用dts作为解码时间戳参考,sink element就需要使用 pts 作为播放时间戳参考。

在特定的时间使用具有特定时间戳的 Buffer,这是消费 Buffer 的 element应该考虑的事情,因此在编写插件的时候需要知道如何根据 pts ,dts, duration 来计算 runningtime。

Buffer running time的计算方法:

To calculate a buffer running-time, we need a buffer timestamp and the SEGMENT event that preceded the buffer. First we can convert the SEGMENT event into a GstSegment object and then we can use the gst_segment_to_running_time () function to perform the calculation of the buffer running-time.

Non-live sources timestamp buffers with a running-time starting from 0. After a flushing seek, they will produce buffers again from a running-time of 0.

Live sources need to timestamp buffers with a running-time matching the pipeline running-time when the first byte of the buffer was captured.

Buffer stream-time:

stream time是指当前Buffer在整个流中的位置,取值范围 0 ~ duration 中间的某个值,stream time用来辅助完成 seek 和 查询操作。不能用来完成 音视频同步等同步工作。

小结:

stream time 和 running time 的区别:

假设一个视频是60s,那么stream time一定是 0~ 60内的某个值,每一个stream time对应视频的一帧图像,如果视频放了30s后被seek到10s,那么这个时候stream time对应的就是 10s的那个时间戳。running time 是持续增长的一个值,表示当前pipeline进入PLAYING状态已经持续了多久,因此即便视频被seek到10s,此时的running time还是一直增长的。

Clock Provider:

前面说到了每个GstPipeline都有一个GstClock实例,也说到了cpu、声卡和系统时间都可以用来作为时钟源。那么具体使用哪个作为时钟源,则需要pipeline中的某个element单担当代理人。一般情况下都是 src element 和 sink element 来担任这个角色,比如audio sink element 需要集成声卡,因此近水楼台方便使用声卡中的时钟。

具体的Clock Provider选择流程为:

When the pipeline goes to the PLAYING state, it will go over all elements in the pipeline from sink to source and ask each element if they can provide a clock. The last element that can provide a clock will be used as the clock provider in the pipeline. This algorithm prefers a clock from an audio sink in a typical playback pipeline and a clock from source elements in a typical capture pipeline.

There exist some bus messages to let you know about the clock and clock providers in the pipeline. You can see what clock is selected in the pipeline by looking at the NEW_CLOCK message on the bus. When a clock provider is removed from the pipeline, a CLOCK_LOST message is posted and the application should go to PAUSED and back to PLAYING to select a new clock.

Latency:

The latency is the time it takes for a sample captured at timestamp X to reach the sink. This time is measured against the clock in the pipeline. For pipelines where the only elements that synchronize against the clock are the sinks, the latency is always 0 since no other element is delaying the buffer.

For pipelines with live sources, a latency is introduced, mostly because of the way a live source works. Consider an audio source, it will start capturing the first sample at time 0. If the source pushes buffers with 44100 samples at a time at 44100Hz it will have collected the buffer at second 1. Since the timestamp of the buffer is 0 and the time of the clock is now >= 1 second, the sink will drop this buffer because it is too late. Without any latency compensation in the sink, all buffers will be dropped.

Latency compensation

Before the pipeline goes to the PLAYING state, it will, in addition to selecting a clock and calculating a base-time, calculate the latency in the pipeline. It does this by doing a LATENCY query on all the sinks in the pipeline. The pipeline then selects the maximum latency in the pipeline and configures this with a LATENCY event.

All sink elements will delay playback by the value in the LATENCY event. Since all sinks delay with the same amount of time, they will be relatively in sync.

Dynamic Latency

Adding/removing elements to/from a pipeline or changing element properties can change the latency in a pipeline. An element can request a latency change in the pipeline by posting a LATENCY message on the bus. The application can then decide to query and redistribute a new latency or not. Changing the latency in a pipeline might cause visual or audible glitches and should therefore only be done by the application when it is allowed.

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的用 GStreamer 实现音视频同步的示例代码: ```python import gi gi.require_version('Gst', '1.0') from gi.repository import Gst, GObject GObject.threads_init() Gst.init(None) player = Gst.ElementFactory.make("playbin", "player") player.set_property("uri", "file:///path/to/media/file") audio_sink = Gst.ElementFactory.make("autoaudiosink", "audio_sink") video_sink = Gst.ElementFactory.make("autovideosink", "video_sink") player.set_property("audio-sink", audio_sink) player.set_property("video-sink", video_sink) bus = player.get_bus() def on_message(bus, message): t = message.type if t == Gst.MessageType.EOS: player.set_state(Gst.State.NULL) loop.quit() elif t == Gst.MessageType.ERROR: err, debug = message.parse_error() print ("Error: %s" % err, debug) player.set_state(Gst.State.NULL) loop.quit() elif t == Gst.MessageType.STATE_CHANGED: if message.src == player: old_state, new_state, pending_state = message.parse_state_changed() if new_state == Gst.State.PLAYING: # Get the current time when playing starts query = Gst.Query.new_seeking(Gst.Format.TIME) if player.query(query): _, start, _ = query.parse_seeking() global start_time start_time = start elif t == Gst.MessageType.QOS: # Get the running time of the pipeline query = Gst.Query.new_position(Gst.Format.TIME) if player.query(query): _, running_time = query.parse_position() running_time += start_time # Get the running time of the last buffer struct = message.get_structure() _, running_time_buffer, _ = struct.get("running-time") running_time_buffer += start_time # Calculate the difference between the two running times diff = running_time - running_time_buffer # If the difference is too big, seek to the correct position if abs(diff) > Gst.SECOND / 10: player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, running_time) bus.add_signal_watch() bus.connect("message", on_message) # Start playing player.set_state(Gst.State.PLAYING) # Start the main loop loop = GObject.MainLoop() loop.run() ``` 此示例中使用了 `autoaudiosink` 和 `autovideosink` 作为音视频的输出,你还可以将其替换为其他的 sink。在收到 QOS 消息时,获取管道的当前时间以及最后一个缓冲区的运行时间,并在两个运行时间之间计算差异,如果差异太大,则使用 `seek_simple()` 函数跳转到正确的位置,以保持音视频同步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值