Gstreamer官方教程彙總基本教程4---Time management

Goal

本教程介紹如何使用GStreamer的時間相關的設置。特別是:html

  • 如何查詢管道的信息好比持流的當前位置和持續時間。web

  • 如何尋求(跳躍)到流的不一樣的位置(時刻)。函數

Introduction

GstQuery  是一種機制,容許向一個元素或襯墊請求一條信息。在這個例子中,咱們詢問管道是否能夠seeking(對於一些源,如實時流,不容許seeking)。若是容許的話,一旦電影已經運行了十秒鐘,咱們使用a seek 跳到一個不一樣的位置。工具

在前面的教程中,一旦咱們有管道安裝和運行,咱們的主要功能就坐着等經過總線接收錯誤或EOS。在這裏,咱們修改這個函數來週期性地喚醒和查詢管道來獲取流的位置,這樣咱們就能夠在屏幕上打印出來。這是相似於一個媒體播放器,按期更新了用戶界面。學習

最後,流持續時間查詢和更新,每當它改變。this

Seeking example

將此代碼複製到名爲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, &current)) {
          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);
}

Walkthrough

/* 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 方法中處理它(下一小節),不然:

User interface resfreshing

/* 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, &current)) {
  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宏簡單標示。

Message Pump

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經過移動滑塊!

Conclusion

本教程顯示:

接下來的教程將介紹如何使用GStreamer的一個圖形用戶界面工具包。

請記住,此頁你應該找到本教程的完整源代碼,並創建它須要的任何附件文件。

很高興在此與你一塊兒度過,並但願在之後的教程繼續見到你!

相關文章
相關標籤/搜索