GStreamer基礎教程06 - 獲取媒體信息

摘要

在常見的媒體文件中,一般包含一些數據(例如:歌手,專輯,編碼類型等),用於描述媒體文件。一般稱這些數據爲元數據(Metadata:data that provides information about other data)。咱們能夠經過這些元數據對媒體進行歸類,同時能夠在播放的過程當中經過界面顯示。本文將介紹GStreamer是如何快速獲取元數據。html

 

GStreamer元數據

GStream將元數據分爲了兩類:緩存

  • 流信息(Stream-info):用於描述流的屬性。例如:編碼類型,分辨率,採樣率等。

Stream-info能夠經過Pipeline中全部的GstCap獲取,使用方式在媒體類型與Pad中有描述,本文將再也不復述。app

  • 流標籤(Stream-tag):用於描述非技術性的信息。例如:做者,標題,專輯等。

Stream-tag能夠經過GstBus,監聽GST_MESSAGE_TAG消息,從消息中提取相應信息。
須要注意的是,Gstreamer可能觸發屢次GST_MESSAGE_TAG消息,應用程序能夠經過gst_tag_list_merge ()合併多個標籤,再在適當的時間顯示,當切換媒體文件時,須要清空緩存。
使用此函數時,須要採用GST_TAG_MERGE_PREPEND,這樣後續更新的元數據會有更高的優先級。async

 

示例代碼

#include <gst/gst.h>

static void
print_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
{
  int i, num;

  num = gst_tag_list_get_tag_size (list, tag);
  for (i = 0; i < num; ++i) {
    const GValue *val;

    /* Note: when looking for specific tags, use the gst_tag_list_get_xyz() API,
     * we only use the GValue approach here because it is more generic */
    val = gst_tag_list_get_value_index (list, tag, i);
    if (G_VALUE_HOLDS_STRING (val)) {
      g_print ("\t%20s : %s\n", tag, g_value_get_string (val));
    } else if (G_VALUE_HOLDS_UINT (val)) {
      g_print ("\t%20s : %u\n", tag, g_value_get_uint (val));
    } else if (G_VALUE_HOLDS_DOUBLE (val)) {
      g_print ("\t%20s : %g\n", tag, g_value_get_double (val));
    } else if (G_VALUE_HOLDS_BOOLEAN (val)) {
      g_print ("\t%20s : %s\n", tag,
          (g_value_get_boolean (val)) ? "true" : "false");
    } else if (GST_VALUE_HOLDS_BUFFER (val)) {
      GstBuffer *buf = gst_value_get_buffer (val);
      guint buffer_size = gst_buffer_get_size (buf);

      g_print ("\t%20s : buffer of size %u\n", tag, buffer_size);
    } else if (GST_VALUE_HOLDS_DATE_TIME (val)) {
      GstDateTime *dt = g_value_get_boxed (val);
      gchar *dt_str = gst_date_time_to_iso8601_string (dt);

      g_print ("\t%20s : %s\n", tag, dt_str);
      g_free (dt_str);
    } else {
      g_print ("\t%20s : tag of type '%s'\n", tag, G_VALUE_TYPE_NAME (val));
    }
  }
}

static void
on_new_pad (GstElement * dec, GstPad * pad, GstElement * fakesink)
{
  GstPad *sinkpad;

  sinkpad = gst_element_get_static_pad (fakesink, "sink");
  if (!gst_pad_is_linked (sinkpad)) {
    if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK)
      g_error ("Failed to link pads!");
  }
  gst_object_unref (sinkpad);
}

int
main (int argc, char ** argv)
{
  GstElement *pipe, *dec, *sink;
  GstMessage *msg;
  gchar *uri;

  gst_init (&argc, &argv);

  if (argc < 2)
    g_error ("Usage: %s FILE or URI", argv[0]);

  if (gst_uri_is_valid (argv[1])) {
    uri = g_strdup (argv[1]);
  } else {
    uri = gst_filename_to_uri (argv[1], NULL);
  }

  pipe = gst_pipeline_new ("pipeline");

  dec = gst_element_factory_make ("uridecodebin", NULL);
  g_object_set (dec, "uri", uri, NULL);
  gst_bin_add (GST_BIN (pipe), dec);

  sink = gst_element_factory_make ("fakesink", NULL);
  gst_bin_add (GST_BIN (pipe), sink);

  g_signal_connect (dec, "pad-added", G_CALLBACK (on_new_pad), sink);

  gst_element_set_state (pipe, GST_STATE_PAUSED);

  while (TRUE) {
    GstTagList *tags = NULL;

    msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe),
        GST_CLOCK_TIME_NONE,
        GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_TAG | GST_MESSAGE_ERROR);

    if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_TAG) /* error or async_done */
      break;

    gst_message_parse_tag (msg, &tags);

    g_print ("Got tags from element %s:\n", GST_OBJECT_NAME (msg->src));
    gst_tag_list_foreach (tags, print_one_tag, NULL);
    g_print ("\n");
    gst_tag_list_unref (tags);

    gst_message_unref (msg);
  }

  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
    GError *err = NULL;

    gst_message_parse_error (msg, &err, NULL);
    g_printerr ("Got error: %s\n", err->message);
    g_error_free (err);
  }

  gst_message_unref (msg);
  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);
  g_free (uri);
  return 0;
}

將源碼保存爲basic-tutorial-6.c,執行下列命令可獲得編譯結果:ide

gcc basic-tutorial-6.c -o basic-tutorial-6 `pkg-config --cflags --libs gstreamer-1.0`函數

示例輸出工具

$ ./basic-tutorial-6 sintel_trailer-480p.ogv
Got tags from element fakesink0:
                       title : Sintel Trailer
                      artist : Durian Open Movie Team
                   copyright : (c) copyright Blender Foundation | durian.blender.org
                     license : Creative Commons Attribution 3.0 license
            application-name : ffmpeg2theora-0.24
                     encoder : Xiph.Org libtheora 1.1 20090822 (Thusnelda)
                 video-codec : Theora
             encoder-version : 3

Got tags from element fakesink0:
            container-format : Ogg

 

源碼分析

本例中使用uridecodebin解析媒體文件,Pipeline的構造與其餘示例相同,下面介紹Tag相關的處理邏輯。源碼分析

static void
print_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
{
  int i, num;

  num = gst_tag_list_get_tag_size (list, tag);
  for (i = 0; i < num; ++i) {
    const GValue *val;

    /* Note: when looking for specific tags, use the gst_tag_list_get_xyz() API,
     * we only use the GValue approach here because it is more generic */
    val = gst_tag_list_get_value_index (list, tag, i);
    if (G_VALUE_HOLDS_STRING (val)) {
      g_print ("\t%20s : %s\n", tag, g_value_get_string (val));
    } 
...
}    

  此函數用於輸出一個標籤的值。GStreamer會將多個標籤都放在同一個GstTagList中。每個標籤能夠包含多個值,因此首先經過gst_tag_list_get_tag_size ()接口及標籤名(tag)獲取其值的數量,而後再獲取相應的值。
  本例使用GValue來進行通用的處理,因此須要先判斷數據的類型,再經過GValue接口獲取。實際處理標籤時,能夠根據規範(例如ID3Tag)獲得標籤值的類型,直接經過GstTagList接口獲取,例如:當標籤名爲title時,咱們能夠直接使用gst_tag_list_get_string()取得title的字符串,不須要再經過GValue轉換,詳細使用方式可參考GstTagList文檔學習

 

static void
on_new_pad (GstElement * dec, GstPad * pad, GstElement * fakesink)
{
  GstPad *sinkpad;

  sinkpad = gst_element_get_static_pad (fakesink, "sink");
  if (!gst_pad_is_linked (sinkpad)) {
    if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK)
      g_error ("Failed to link pads!");
  }
  gst_object_unref (sinkpad);
}
...
  sink = gst_element_factory_make ("fakesink", NULL);
  gst_bin_add (GST_BIN (pipe), sink);
  g_signal_connect (dec, "pad-added", G_CALLBACK (on_new_pad), sink);

  因爲咱們只須要提取相應的媒體信息,不須要關心具體的數據,因此這裏使用了fakesink,fakesink會直接丟棄掉全部收到的數據。同時在此處監聽了"pad-added"的信號,用於動態鏈接Pipeline,這種處理方式已在動態鏈接Pipeline中進行了詳細的介紹。ui

 

  while (TRUE) {
    GstTagList *tags = NULL;

    msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe),
        GST_CLOCK_TIME_NONE,
        GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_TAG | GST_MESSAGE_ERROR);

    if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_TAG) /* error or async_done */
      break;

    gst_message_parse_tag (msg, &tags);

    g_print ("Got tags from element %s:\n", GST_OBJECT_NAME (msg->src));
    gst_tag_list_foreach (tags, print_one_tag, NULL);
    g_print ("\n");
    gst_tag_list_unref (tags);

    gst_message_unref (msg);
  }

  與其餘示例相同,這裏也採用gst_bus_timed_pop_filtered()獲取Bus上的GST_MESSAGE_TAG,再經過gst_message_parse_tag ()從消息中將標籤拷貝到GstTagList中,再經過gst_tag_list_foreach ()依次輸出全部的標籤,隨後釋放GstTagList。
  須要注意的是,若是GstTagList中不包含任何標籤信息,gst_tag_list_foreach ()中的回調函數不會被調用。

 

  從上面的介紹能夠發現,Stream-tag主要是經過監聽GST_MESSAGE_TAG後,根據相應接口提取元數據。在使用的過程當中須要注意數據的釋放。

 

GstDiscoverer

  獲取媒體信息是一個經常使用的功能,所以GStreamer經過GstDiscoverer提供了一組實用接口。使用時無需關心內部Pipeline的建立,只需經過gst_discoverer_new()建立實例,使用gst_discoverer_discover_uri()指定URI,監聽相應信號後,便可在回調函數中獲得相應的元數據,使用時須要額外鏈接libgstpbutils-1.0庫。GStreamer同時基於GstDiscoverer提供了gst-discoverer-1.0工具,使用方式以下:

$ gst-discoverer-1.0 sintel_trailer-480p.mp4
Analyzing file:///home/xleng/video/sintel_trailer-480p.mp4
Done discovering file:///home/xleng/video/sintel_trailer-480p.mp4

Topology:
  container: Quicktime
    audio: MPEG-4 AAC
    video: H.264 (High Profile)

Properties:
  Duration: 0:00:52.209000000
  Seekable: yes
  Live: no
  Tags:
      audio codec: MPEG-4 AAC audio
      maximum bitrate: 128000
      datetime: 1970-01-01T00:00:00Z
      title: Sintel Trailer
      artist: Durian Open Movie Team
      copyright: (c) copyright Blender Foundation | durian.blender.org
      description: Trailer for the Sintel open movie project
      encoder: Lavf52.62.0
      container format: ISO MP4/M4A
      video codec: H.264 / AVC
      bitrate: 535929

 

總結

在本教程中,咱們學習了:

  • 如何經過GST_MESSAGE_TAG獲得全部的標籤信息。
  • 如何經過gst_message_parse_tag ()將消息轉換爲GstTagList。
  • 如何經過GstTagList的接口取得相應標籤的數據。
  • gst-discoverer命令的使用。

後續咱們將介紹如何控制GStreamer的播放速度。

 

引用

https://gstreamer.freedesktop.org/documentation/tutorials/basic/media-information-gathering.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/application-development/advanced/metadata.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/application-development/basics/bus.html?gi-language=c

 

做者: John.Leng
本文版權歸做者全部,歡迎轉載。商業轉載請聯繫做者得到受權,非商業轉載請在文章頁面明顯位置給出原文鏈接.
相關文章
相關標籤/搜索