Gstreamer的音视频同步

(Original: http://blog.csdn.net/maeom/article/details/7729840)

一 概述

    Gstreamer的音频视频同步,概括起来是一个比较大的问题,因为在网上可以看到很多音视频同步的办法。这里我们只看最普通的一种。以音频时钟做为参考时钟(要求参考时钟上的时间是线性递增的);生成数据流时依据参考时钟上的时间给每个数据块都打上时间戳(一般包括开始时间和结束时间);在播放时,读取数据上的时间戳,同时参考当前参考时钟上的时间来安排播放(如果数据块上的时间大于参考时钟的时间,则不急于播放,直到参考时钟达到数据块的开始时间;如果数据块上的时间小于参考时钟的时间,则应"尽快"播放或者干脆"丢弃"该数据块,以使得播放赶上播放进度。)

    Gstreamer的因视频分离器如下图:

    demux element将音频,视频分离后,给各自的解码器进行解码播放。      

[cpp]  view plain copy
  1.                   +-----------+  
  2.                   |   Audio   |  
  3.                +--|           |  
  4.               /   +-----------+  
  5. +----------+ /  
  6. |  demux   |/  
  7. |          |\  
  8. +----------+ \  
  9.               \   +-----------+  
  10.                +--|   Video   |  
  11.                   |           |  
  12.                   +-----------+  

二  提供时钟

    默认情况下,是有AudioSink来提供参考时钟的。下面开始代码之旅。

[cpp]  view plain copy
  1. /* gst-plugins-base-0.10.32/gst-libs/gst/audio/gstbaseaudiosink.c */  
  2. /*默认的情况下是由这个element来提供clock的。*/  
  3. #define DEFAULT_PROVIDE_CLOCK   TRUE  
  4.   
  5. static void  
  6. gst_base_audio_sink_init (GstBaseAudioSink * baseaudiosink,  
  7.     GstBaseAudioSinkClass * g_class)  
  8. {  
  9.   baseaudiosink->provide_clock = DEFAULT_PROVIDE_CLOCK  
  10.   /* 这里在clock类里面新建了一个时钟 */  
  11.   baseaudiosink->provided_clock = gst_audio_clock_new ("GstAudioSinkClock",  
  12.     (GstAudioClockGetTimeFunc) gst_base_audio_sink_get_time, baseaudiosink);  
  13. }  
  14.   
  15. /* 
  16.  * 查询是否 @sink 将提供 clock 
  17.  */  
  18. gboolean  
  19. gst_base_audio_sink_get_provide_clock (GstBaseAudioSink * sink)  
  20. {  
  21.   gboolean result;  
  22.   result = sink->provide_clock;  
  23.   return result;  
  24. }  
  25.   
  26. /* 查询clock的时间 
  27.  * 如果将这里的返回结果变慢,那么视频播放就会变慢。当然视频很音频就不同步了。 
  28.  */  
  29. static GstClockTime  
  30. gst_base_audio_sink_get_time (GstClock * clock, GstBaseAudioSink * sink)  
  31. {  
  32.   result = gst_util_uint64_scale_int (samples, GST_SECOND,  
  33.       sink->ringbuffer->spec.rate);  
  34.   return result;  
  35. }  

三 视频如何同步?

    以我实验的视频为例,视频使用的是xvimagesink element它的继承关系如下

[cpp]  view plain copy
  1. GObject  
  2.  +----GstObject  
  3.        +----GstElement  
  4.              +----GstBaseSink  
  5.                    +----GstVideoSink  
  6.                          +----GstXvImageSink  
 从element的chain func开始(PS: 为什么从chain开始,参考[Gstreamer初见]).

[cpp]  view plain copy
  1. /* 
  2.  * gst-plugins-base/sys/xvimage/xvimagesink.c 
  3.  * gst-plugins-base/gst-libs/gst/video/gstvideosink.c 
  4.  * 这两个文件里都没有chain函数. 
  5.  * 在gstreamer-0.10.32/libs/gst/base/gstbasesink.c中 chain函数为 
  6.  */  
  7. static GstFlowReturn  
  8. gst_base_sink_chain (GstPad * pad, GstBuffer * buf)  
  9. {  
  10.   basesink = GST_BASE_SINK (GST_OBJECT_PARENT (pad));  
  11.   
  12.   return gst_base_sink_chain_main (basesink, pad, _PR_IS_BUFFER, buf);  
  13. }  
  14.   
  15. static GstFlowReturn  
  16. gst_base_sink_chain_main (GstBaseSink * basesink, GstPad * pad,  
  17.     guint8 obj_type, gpointer obj)  
  18. {  
  19.   result = gst_base_sink_chain_unlocked (basesink, pad, obj_type, obj);  
  20. }  
  21.   
  22. static GstFlowReturn  
  23. gst_base_sink_chain_unlocked (GstBaseSink * basesink, GstPad * pad,  
  24.     guint8 obj_type, gpointer obj)  
  25. {  
  26.   result = gst_base_sink_queue_object_unlocked (basesink, pad,  
  27.       obj_type, obj, TRUE);  
  28. }  
  29.   
  30. static GstFlowReturn  
  31. gst_base_sink_queue_object_unlocked (GstBaseSink * basesink, GstPad * pad,  
  32.     guint8 obj_type, gpointer obj, gboolean prerollable)   
  33. {  
  34.   while (G_UNLIKELY (!g_queue_is_empty (q))) {  
  35.     ret = gst_base_sink_render_object (basesink, pad, ot, o);  
  36.   }  
  37. }  
  38.   
  39. /* gstreamer-0.10.32/libs/gst/base/gstbasesink.c */  
  40. static GstFlowReturn  
  41. gst_base_sink_render_object (GstBaseSink * basesink, GstPad * pad,  
  42.      guint8 obj_type, gpointer obj)  
  43. {  
  44.   /* 这里开始做同步,同步成功后,才开始播放 */  
  45.   ret =  
  46.      gst_base_sink_do_sync (basesink, pad, sync_obj, &late, &step_end,  
  47.      obj_type);  
  48.   if (G_UNLIKELY (ret != GST_FLOW_OK))  
  49.     goto sync_failed;  
  50.   
  51.   if (!OBJ_IS_BUFFERLIST (obj_type)) {  
  52.     ret = bclass->render (basesink, buf);  
  53.   } else {  
  54.     ret = bclass->render_list (basesink, buflist);  
  55.   }  
  56. }  
  57.   
  58. static GstFlowReturn  
  59. gst_base_sink_do_sync (GstBaseSink * basesink, GstPad * pad,  
  60.     GstMiniObject * obj, gboolean * late, gboolean * step_end, guint8 obj_type)  
  61. {  
  62.   status = gst_base_sink_wait_clock (basesink, stime, &jitter);  
  63.   return GST_FLOW_OK;  
  64. }  
  65.   
  66.   
  67. /* 
  68.  * @time: the running_time to be reached 
  69.  * @jitter: (out) (allow-none): the jitter to be filled with time diff, or NULL 
  70.  * 
  71.  * This function will block until @time is reached. It is usually called by 
  72.  * subclasses that use their own internal synchronisation. 
  73.  */  
  74. GstClockReturn  
  75. gst_base_sink_wait_clock (GstBaseSink * sink, GstClockTime time,  
  76.     GstClockTimeDiff * jitter)  
  77. {  
  78.   if (G_UNLIKELY ((clock = GST_ELEMENT_CLOCK (sink)) == NULL))  
  79.     goto no_clock;  
  80.   
  81.   base_time = GST_ELEMENT_CAST (sink)->base_time;  
  82.   
  83.   sink->priv->cached_clock_id = gst_clock_new_single_shot_id (clock, time);  
  84.   
  85.   /* 这里一直等待到时间 */  
  86.   ret = gst_clock_id_wait (sink->priv->cached_clock_id, jitter);  
  87.   
  88.   return ret;  
  89. }  

    这里同步完成,其实这里还有最后一个小问题,那么就是AudioClock是以什么为时钟的呢。其实就是以声卡的时钟为时钟的。因为声卡有时钟同步功能。所以我们计算一同播放了多少个sample,就可以计算出当前播放了多长的时间。 So. 

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值