摘要
在常见的媒体文件中, 通常包含一些数据(例如: 歌手, 专辑, 编码类型等), 用于描述媒体文件. 通常称这些数据为元数据(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 的播放速度.
引用
作者: John.Leng
来源: https://www.cnblogs.com/xleng/p/11277397.html