一 概述
Gstreamer的音頻視頻同步,概括起來是一個比較大的問題,因爲在網上可以看到很多音視頻同步的辦法。這裏我們只看最普通的一種。以音頻時鐘做爲參考時鐘(要求參考時鐘上的時間是線性遞增的);生成數據流時依據參考時鐘上的時間給每個數據塊都打上時間戳(一般包括開始時間和結束時間);在播放時,讀取數據上的時間戳,同時參考當前參考時鐘上的時間來安排播放(如果數據塊上的時間大於參考時鐘的時間,則不急於播放,直到參考時鐘達到數據塊的開始時間;如果數據塊上的時間小於參考時鐘的時間,則應"儘快"播放或者乾脆"丟棄"該數據塊,以使得播放趕上播放進度。)
Gstreamer的音視頻分離器如下圖:
demux element將音頻,視頻分離後,給各自的解碼器進行解碼播放。
- +-----------+
- | Audio |
- +--| |
- / +-----------+
- +----------+ /
- | demux |/
- | |\
- +----------+ \
- \ +-----------+
- +--| Video |
- | |
- +-----------+
二 提供時鐘
默認情況下,是有AudioSink來提供參考時鐘的。下面開始代碼之旅。
- /* gst-plugins-base-0.10.32/gst-libs/gst/audio/gstbaseaudiosink.c */
- /*默認的情況下是由這個element來提供clock的。*/
- #define DEFAULT_PROVIDE_CLOCK TRUE
- static void
- gst_base_audio_sink_init (GstBaseAudioSink * baseaudiosink,
- GstBaseAudioSinkClass * g_class)
- {
- baseaudiosink->provide_clock = DEFAULT_PROVIDE_CLOCK
- /* 這裏在clock類裏面新建了一個時鐘 */
- baseaudiosink->provided_clock = gst_audio_clock_new ("GstAudioSinkClock",
- (GstAudioClockGetTimeFunc) gst_base_audio_sink_get_time, baseaudiosink);
- }
- /*
- * 查詢是否 @sink 將提供 clock
- */
- gboolean
- gst_base_audio_sink_get_provide_clock (GstBaseAudioSink * sink)
- {
- gboolean result;
- result = sink->provide_clock;
- return result;
- }
- /* 查詢clock的時間
- * 如果將這裏的返回結果變慢,那麼視頻播放就會變慢。當然視頻很音頻就不同步了。
- */
- static GstClockTime
- gst_base_audio_sink_get_time (GstClock * clock, GstBaseAudioSink * sink)
- {
- result = gst_util_uint64_scale_int (samples, GST_SECOND,
- sink->ringbuffer->spec.rate);
- return result;
- }
以我實驗的視頻爲例,視頻使用的是xvimagesink element它的繼承關係如下
- GObject
- +----GstObject
- +----GstElement
- +----GstBaseSink
- +----GstVideoSink
- +----GstXvImageSink
- /*
- * gst-plugins-base/sys/xvimage/xvimagesink.c
- * gst-plugins-base/gst-libs/gst/video/gstvideosink.c
- * 這兩個文件裏都沒有chain函數.
- * 在gstreamer-0.10.32/libs/gst/base/gstbasesink.c中 chain函數爲
- */
- static GstFlowReturn
- gst_base_sink_chain (GstPad * pad, GstBuffer * buf)
- {
- basesink = GST_BASE_SINK (GST_OBJECT_PARENT (pad));
- return gst_base_sink_chain_main (basesink, pad, _PR_IS_BUFFER, buf);
- }
- static GstFlowReturn
- gst_base_sink_chain_main (GstBaseSink * basesink, GstPad * pad,
- guint8 obj_type, gpointer obj)
- {
- result = gst_base_sink_chain_unlocked (basesink, pad, obj_type, obj);
- }
- static GstFlowReturn
- gst_base_sink_chain_unlocked (GstBaseSink * basesink, GstPad * pad,
- guint8 obj_type, gpointer obj)
- {
- result = gst_base_sink_queue_object_unlocked (basesink, pad,
- obj_type, obj, TRUE);
- }
- static GstFlowReturn
- gst_base_sink_queue_object_unlocked (GstBaseSink * basesink, GstPad * pad,
- guint8 obj_type, gpointer obj, gboolean prerollable)
- {
- while (G_UNLIKELY (!g_queue_is_empty (q))) {
- ret = gst_base_sink_render_object (basesink, pad, ot, o);
- }
- }
- /* gstreamer-0.10.32/libs/gst/base/gstbasesink.c */
- static GstFlowReturn
- gst_base_sink_render_object (GstBaseSink * basesink, GstPad * pad,
- guint8 obj_type, gpointer obj)
- {
- /* 這裏開始做同步,同步成功後,纔開始播放 */
- ret =
- gst_base_sink_do_sync (basesink, pad, sync_obj, &late, &step_end,
- obj_type);
- if (G_UNLIKELY (ret != GST_FLOW_OK))
- goto sync_failed;
- if (!OBJ_IS_BUFFERLIST (obj_type)) {
- ret = bclass->render (basesink, buf);
- } else {
- ret = bclass->render_list (basesink, buflist);
- }
- }
- static GstFlowReturn
- gst_base_sink_do_sync (GstBaseSink * basesink, GstPad * pad,
- GstMiniObject * obj, gboolean * late, gboolean * step_end, guint8 obj_type)
- {
- status = gst_base_sink_wait_clock (basesink, stime, &jitter);
- return GST_FLOW_OK;
- }
- /*
- * @time: the running_time to be reached
- * @jitter: (out) (allow-none): the jitter to be filled with time diff, or NULL
- *
- * This function will block until @time is reached. It is usually called by
- * subclasses that use their own internal synchronisation.
- */
- GstClockReturn
- gst_base_sink_wait_clock (GstBaseSink * sink, GstClockTime time,
- GstClockTimeDiff * jitter)
- {
- if (G_UNLIKELY ((clock = GST_ELEMENT_CLOCK (sink)) == NULL))
- goto no_clock;
- base_time = GST_ELEMENT_CAST (sink)->base_time;
- sink->priv->cached_clock_id = gst_clock_new_single_shot_id (clock, time);
- /* 這裏一直等待到時間 */
- ret = gst_clock_id_wait (sink->priv->cached_clock_id, jitter);
- return ret;
- }
這裏同步完成,其實這裏還有最後一個小問題,那麼就是AudioClock是以什麼爲時鐘的呢。其實就是以聲卡的時鐘爲時鐘的。因爲聲卡有時鐘同步功能。所以我們計算一同播放了多少個sample,就可以計算出當前播放了多長的時間。 So.
END