摘要
在常見的媒體檔案中,通常包含一些資料(例如:歌手,專輯,編碼型別等),用於描述媒體檔案。通常稱這些資料為後設資料(Metadata:data that provides information about other data)。我們可以通過這些後設資料對媒體進行歸類,同時可以在播放的過程中通過介面顯示。本文將介紹GStreamer是如何快速獲取後設資料。
GStreamer後設資料
GStream將後設資料分為了兩類:
- 流資訊(Stream-info):用於描述流的屬性。例如:編碼型別,解析度,取樣率等。
Stream-info可以通過Pipeline中所有的GstCap獲取,使用方式在媒體型別與Pad中有描述,本文將不再複述。
- 流標籤(Stream-tag):用於描述非技術性的資訊。例如:作者,標題,專輯等。
Stream-tag可以通過GstBus,監聽GST_MESSAGE_TAG訊息,從訊息中提取相應資訊。
需要注意的是,Gstreamer可能觸發多次GST_MESSAGE_TAG訊息,應用程式可以通過gst_tag_list_merge ()合併多個標籤,再在適當的時間顯示,當切換媒體檔案時,需要清空快取。
使用此函式時,需要採用GST_TAG_MERGE_PREPEND,這樣後續更新的後設資料會有更高的優先順序。
示例程式碼
#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,執行下列命令可得到編譯結果:
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中進行了詳細的介紹。
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