本教程介紹如何使用GStreamer的時間相關的設置。特別是:html
如何查詢管道的信息好比持流的當前位置和持續時間。web
如何尋求(跳躍)到流的不一樣的位置(時刻)。函數
GstQuery
是一種機制,容許向一個元素或襯墊請求一條信息。在這個例子中,咱們詢問管道是否能夠seeking(對於一些源,如實時流,不容許seeking)。若是容許的話,一旦電影已經運行了十秒鐘,咱們使用a seek 跳到一個不一樣的位置。工具
在前面的教程中,一旦咱們有管道安裝和運行,咱們的主要功能就坐着等經過總線接收錯誤或EOS。在這裏,咱們修改這個函數來週期性地喚醒和查詢管道來獲取流的位置,這樣咱們就能夠在屏幕上打印出來。這是相似於一個媒體播放器,按期更新了用戶界面。學習
最後,流持續時間查詢和更新,每當它改變。this
將此代碼複製到名爲basic-tutorial-4.c
一個文本文件:編碼
#include <gst/gst.h> /* Structure to contain all our information, so we can pass it around */ typedef struct _CustomData { GstElement *playbin2; /* Our one and only element */ gboolean playing; /* Are we in the PLAYING state? */ gboolean terminate; /* Should we terminate execution? */ gboolean seek_enabled; /* Is seeking enabled for this media? */ gboolean seek_done; /* Have we performed the seek already? */ gint64 duration; /* How long does this media last, in nanoseconds */ } CustomData; /* Forward definition of the message processing function */ static void handle_message (CustomData *data, GstMessage *msg); int main(int argc, char *argv[]) { CustomData data; GstBus *bus; GstMessage *msg; GstStateChangeReturn ret; data.playing = FALSE; data.terminate = FALSE; data.seek_enabled = FALSE; data.seek_done = FALSE; data.duration = GST_CLOCK_TIME_NONE; /* Initialize GStreamer */ gst_init (&argc, &argv); /* Create the elements */ data.playbin2 = gst_element_factory_make ("playbin2", "playbin2"); if (!data.playbin2) { g_printerr ("Not all elements could be created.\n"); return -1; } /* Set the URI to play */ g_object_set (data.playbin2, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL); /* Start playing */ ret = gst_element_set_state (data.playbin2, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { g_printerr ("Unable to set the pipeline to the playing state.\n"); gst_object_unref (data.playbin2); return -1; } /* Listen to the bus */ bus = gst_element_get_bus (data.playbin2); do { msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND, GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION); /* Parse message */ if (msg != NULL) { handle_message (&data, msg); } else { /* We got no message, this means the timeout expired */ if (data.playing) { GstFormat fmt = GST_FORMAT_TIME; gint64 current = -1; /* Query the current position of the stream */ if (!gst_element_query_position (data.playbin2, &fmt, ¤t)) { g_printerr ("Could not query current position.\n"); } /* If we didn't know it yet, query the stream duration */ if (!GST_CLOCK_TIME_IS_VALID (data.duration)) { if (!gst_element_query_duration (data.playbin2, &fmt, &data.duration)) { g_printerr ("Could not query current duration.\n"); } } /* Print current position and total duration */ g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r", GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration)); /* If seeking is enabled, we have not done it yet, and the time is right, seek */ if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) { g_print ("\nReached 10s, performing seek...\n"); gst_element_seek_simple (data.playbin2, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND); data.seek_done = TRUE; } } } } while (!data.terminate); /* Free resources */ gst_object_unref (bus); gst_element_set_state (data.playbin2, GST_STATE_NULL); gst_object_unref (data.playbin2); return 0; } static void handle_message (CustomData *data, GstMessage *msg) { GError *err; gchar *debug_info; switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_ERROR: gst_message_parse_error (msg, &err, &debug_info); g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); g_clear_error (&err); g_free (debug_info); data->terminate = TRUE; break; case GST_MESSAGE_EOS: g_print ("End-Of-Stream reached.\n"); data->terminate = TRUE; break; case GST_MESSAGE_DURATION: /* The duration has changed, mark the current one as invalid */ data->duration = GST_CLOCK_TIME_NONE; break; case GST_MESSAGE_STATE_CHANGED: { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin2)) { g_print ("Pipeline state changed from %s to %s:\n", gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); /* Remember whether we are in the PLAYING state or not */ data->playing = (new_state == GST_STATE_PLAYING); if (data->playing) { /* We just moved to PLAYING. Check if seeking is possible */ GstQuery *query; gint64 start, end; query = gst_query_new_seeking (GST_FORMAT_TIME); if (gst_element_query (data->playbin2, query)) { gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end); if (data->seek_enabled) { g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (start), GST_TIME_ARGS (end)); } else { g_print ("Seeking is DISABLED for this stream.\n"); } } else { g_printerr ("Seeking query failed."); } gst_query_unref (query); } } } break; default: /* We should not reach here */ g_printerr ("Unexpected message received.\n"); break; } gst_message_unref (msg); }
/* Structure to contain all our information, so we can pass it around */ typedef struct _CustomData { GstElement *playbin2; /* Our one and only element */ gboolean playing; /* Are we in the PLAYING state? */ gboolean terminate; /* Should we terminate execution? */ gboolean seek_enabled; /* Is seeking enabled for this media? */ gboolean seek_done; /* Have we performed the seek already? */ gint64 duration; /* How long does this media last, in nanoseconds */ } CustomData; /* Forward definition of the message processing function */ static void handle_message (CustomData *data, GstMessage *msg);
咱們從定義包含全部咱們的信息的結構體開始,因此咱們能夠圍繞它傳遞給其餘函數。特別是,在這個例子中,咱們將消息處理代碼移動到它本身的方法 handle_message 中,由於它的代碼太多了。spa
而後,咱們將創建一個單一元素組成的流水線,一個 playbin2
,這是咱們在 Basic tutorial 1: Hello world! 已經看到。然而,playbin2 自己是一個管道,而且在這種狀況下,它是在管道中的惟一的元素,因此咱們使用直接playbin2元件。咱們將跳過細節:clip的URI經過URI屬性給予 playbin2 和將管道設置爲播放狀態。debug
msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND, GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);
之前咱們沒有提供一個超時信號給 gst_bus_timed_pop_filtered()
,這意味着它沒有返回,直到收到一個消息。如今咱們設置100毫秒爲超時時間,因此,若是沒有接收到消息時,每秒中有10次將一個NULL代替GstMessage返回。咱們將用它來更新咱們的「用戶界面」。請注意,超時時間被指定在納秒,因此使用 GST_SECOND或GST_MSECOND宏定義,強烈推薦。code
若是咱們獲得了一個消息,咱們在 handle_message 方法中處理它(下一小節),不然:
/* We got no message, this means the timeout expired */ if (data.playing) {
首先,若是咱們不是在播放狀態下,咱們不想在這裏作任何事,由於大多數的查詢會失敗。不然,那就該刷新屏幕了。
咱們在這裏大約每秒10次,對於咱們的UI來講時一個足夠好的刷新率。咱們將在屏幕上打印當前的媒體位置,這是咱們能夠學習能夠查詢管道。這涉及到將在下一小節要顯示了幾步,但因爲位置和持續時間時很常見的查詢,繼承 GstElement 則更簡單,現成的選擇:
/* Query the current position of the stream */ if (!gst_element_query_position (data.pipeline, &fmt, ¤t)) { g_printerr ("Could not query current position.\n"); }
gst_element_query_position() 隱藏了查詢對象的管理,並直接爲咱們提供的結果。
/* If we didn't know it yet, query the stream duration */ if (!GST_CLOCK_TIME_IS_VALID (data.duration)) { if (!gst_element_query_duration (data.pipeline, &fmt, &data.duration)) { g_printerr ("Could not query current duration.\n"); } }
如今是一個很好的時機,知道流長度,與另外一繼承 GstElement 輔助函數: gst_element_query_duration()
/* Print current position and total duration */ g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r", GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));
注意GST_TIME_FORMAT和GST_TIME_ARGS提供的將GStreamer時間轉換爲對用戶友好的表示。
/* If seeking is enabled, we have not done it yet, and the time is right, seek */ if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) { g_print ("\nReached 10s, performing seek...\n"); gst_element_seek_simple (data.pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND); data.seek_done = TRUE; }
如今咱們進行seek,「簡單的」在管道上調用 gst_element_seek_simple()
。不少seeking的複雜性都隱藏在這種方法中,這是一件好事!
讓咱們回顧一下參數:
GST_FORMAT_TIME代表咱們正在指定目的地的時間,由於對面的字節(和其餘比較模糊的機制)。
隨之而來的GstSeekFlags,讓咱們回顧一下最多見的:
GST_SEEK_FLAG_FLUSH:在seeking以前丟棄目前在管道中的全部數據。可能稍微暫停,而管道回填和新的數據開始到來,但大大增長了應用程序的「可響應性」。若是不提供這個標誌,「過期」的數據可能會顯示一段時間,直到新的位置出如今管道的末端。
GST_SEEK_FLAG_KEY_UNIT:大多數編碼的視頻流沒法尋求到任意位置,只有某些幀稱爲關鍵幀。當使用該標誌時,seek將實際移動到最近的關鍵幀,並開始生產數據,立竿見影。若是不使用這個標誌,管道將在內部移動至最近的關鍵幀(它沒有其餘選擇),但數據不會被顯示,直到達到要求的位置。不提供該標誌是更準確的,但可能須要更長的時間進行反應。
GST_SEEK_FLAG_ACCURATE:有些媒體剪輯不能提供足夠的索引信息,這意味着尋求任意位置很是耗時。在這些狀況下,GStreamer的一般在估計的位置尋求,一般工做得很好。若是精度的要求對你來講無所謂,而後提供該標誌。被警告,它可能須要更長的時間來計算(很是長,對一些文件)。
最後,咱們提供seek的位置。因爲咱們要求GST_FORMAT_TIME,這個位置是在納秒,因此咱們使用GST_SECOND宏簡單標示。
該handle_message函數處理經過管道的總線上接收的全部消息。錯誤和EOS的處理是同樣的在前面的教程,因此咱們跳到感興趣的部分:
case GST_MESSAGE_DURATION: /* The duration has changed, mark the current one as invalid */ data->duration = GST_CLOCK_TIME_NONE; break;
此消息發佈到總線上,不論流的持續時間是否變化。在這裏,咱們簡單地將目前的持續時間爲無效,那麼它就會被後來從新查詢。
case GST_MESSAGE_STATE_CHANGED: { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) { g_print ("Pipeline state changed from %s to %s:\n", gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); /* Remember whether we are in the PLAYING state or not */ data->playing = (new_state == GST_STATE_PLAYING);
Seeks和時間查詢在處於暫停或播放狀態下工做得更好,由於全部的元素都有一個不得不接受信息和配置本身的機會。在這裏,咱們注意到,咱們經過的playing變量能夠斷定是否處於播放狀態。
此外,若是咱們剛進入播放狀態時,咱們作咱們的第一個查詢。咱們詢問管道,在當前流中是否支持seeking:
if (data->playing) { /* We just moved to PLAYING. Check if seeking is possible */ GstQuery *query; gint64 start, end; query = gst_query_new_seeking (GST_FORMAT_TIME); if (gst_element_query (data->pipeline, query)) { gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end); if (data->seek_enabled) { g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (start), GST_TIME_ARGS (end)); } else { g_print ("Seeking is DISABLED for this stream.\n"); } } else { g_printerr ("Seeking query failed."); } gst_query_unref (query); }
gst_query_new_seeking()
建立一個「seeking」型的、GST_FORMAT_TIME格式的新的查詢對象。這代表,咱們感興趣的是經過指定一個新的咱們要移動的時間來seeking。咱們也能夠要求GST_FORMAT_BYTES,並尋求到源文件中特定的字節位置,但正常狀況下這沒多少益處。
那麼這個查詢對象經過方法 gst_element_query()
傳遞到管道。結果被存儲在相同的查詢,而且能夠很容易地經過 gst_query_parse_seeking()
檢索到。它提取一個表示是否容許seeking的布爾值,和可seeking的範圍。
不要忘了釋放查詢對象。
就是這樣!有了這些知識,咱們能夠構建一個週期性更新滑塊的媒體播放器,並能夠在當前流位置,容許seeking經過移動滑塊!
本教程顯示:
如何使用 GstQuery
查詢管道信息
如何使用 gst_element_query_position() 和 gst_element_query_duration(),以得到相似的位置和持續時間共同信息
如何使用 gst_element_seek_simple()
尋求在流中的任意位置
在其中規定全部這些操做均可以進行
接下來的教程將介紹如何使用GStreamer的一個圖形用戶界面工具包。
請記住,此頁你應該找到本教程的完整源代碼,並創建它須要的任何附件文件。
很高興在此與你一塊兒度過,並但願在之後的教程繼續見到你!