摘要

在常见的媒体文件中,通常包含一些数据(例如:歌手,专辑,编码类型等),用于描述媒体文件。通常称这些数据为元数据(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 = ; 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 < )
g_error ("Usage: %s FILE or URI", argv[]); if (gst_uri_is_valid (argv[])) {
uri = g_strdup (argv[]);
} else {
uri = gst_filename_to_uri (argv[], 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 ;
}

将源码保存为basic-tutorial-6.c,执行下列命令可得到编译结果:

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

示例输出

$ ./basic-tutorial- 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 (Thusnelda)
video-codec : Theora
encoder-version : 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 = ; 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- AAC
video: H. (High Profile) Properties:
Duration: ::52.209000000
Seekable: yes
Live: no
Tags:
audio codec: MPEG- AAC audio
maximum bitrate:
datetime: --01T00::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. / AVC
bitrate:

总结

在本教程中,我们学习了:

  • 如何通过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
本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.

GStreamer基础教程06 - 获取媒体信息的更多相关文章

  1. 【GStreamer开发】GStreamer基础教程09——收集媒体信息

    目标 有时你需要快速的了解一个文件(或URI)包含的媒体格式或者看看是否支持这种格式.当然你可以创建一个pipeline,设置运行,观察总线上的消息,但GStreamer提供了一个工具可以帮你做这些. ...

  2. 【GStreamer开发】GStreamer基础教程06——媒体格式和pad的Capabilities

    目标       Pad的Capabilities是一个GStreamer element的基础,因为framework大部分时间是自动处理的,所以我们几乎感觉不到它的存在.本教程比较偏向原理,介绍了 ...

  3. GStreamer基础教程12 - 常用命令工具

    摘要 GStreamer提供了不同的命令行工具用于快速的查看信息以及验证Pipeline的是否能够正确运行,在平时的开发过程中,我们也优先使用GStreamer的命令行工具验证,再将Pipeline集 ...

  4. 【GStreamer开发】GStreamer基础教程13——播放速度

    目标 快进,倒放和慢放是trick模式的共同技巧,它们有一个共同点就是它们都修改了播放的速度.本教程会展示如何来获得这些效果和如何进行逐帧的跳跃.主要内容是: 如何来变换播放的速度,变快或者变慢,前进 ...

  5. 【GStreamer开发】GStreamer基础教程14——常用的element

    目标 本教程给出了一系列开发中常用的element.它们包括大杂烩般的eleemnt(比如playbin2)以及一些调试时很有用的element. 简单来说,下面用gst-launch这个工具给出一个 ...

  6. 【GStreamer开发】GStreamer基础教程10——GStreamer工具

    目标 GStreamer提供了一系列方便使用的工具.这篇教程里不牵涉任何代码,但还是会讲一些有用的内容: 如何在命令行下建立一个pipeline--完全不使用C 如何找出一个element的Capab ...

  7. GStreamer基础教程09 - Appsrc及Appsink

    摘要 在我们前面的文章中,我们的Pipline都是使用GStreamer自带的插件去产生/消费数据.在实际的情况中,我们的数据源可能没有相应的gstreamer插件,但我们又需要将数据发送到GStre ...

  8. 【GStreamer开发】GStreamer基础教程08——pipeline的快捷访问

    目标 GStreamer建立的pipeline不需要完全关闭.有多种方法可以让数据在任何时候送到pipeline中或者从pipeline中取出.本教程会展示: 如何把外部数据送到pipeline中 如 ...

  9. 【GStreamer开发】GStreamer基础教程05——集成GUI工具

    目标 本教程展示了如何在GStreamer集成一个GUI(比如:GTK+).最基本的原则是GStreamer处理多媒体的播放而GUI处理和用户的交互. 在这个教程里面,我们可以学到: 如何告诉GStr ...

随机推荐

  1. 节能减排到底如何----google earth engine 告诉你!!

    (First,再次严谨说明,本人成果未经允许,切勿发表到相关学术期刊,如果有技术交流,qq1044625113,顺便打个广告,兼职GEE开发,欢迎联系!) 终于过了严寒的冬天,2017年的冬天中国南方 ...

  2. 什么是 VxLAN?

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. VLAN 和 ...

  3. Linux 中 IDEA 不能调试(Debug)项目

    问题描述: can't debug project on idea linux. 在Linux 中, IDEA能运行项目,但是点击调试项目,弹出警告.警告内容如下: Required connecto ...

  4. JAVA写入大文件DEMO

    /**     * 读取filePath的文件     * @param filePath    文件的路径     * @return     List集合       文件中一行一行的数据     ...

  5. easyui close的最大化的dialog 切换 tab 再次出现

    今天发现一个神奇的bug,easyui中的dialog在经历了d.panel('close');之后,当前的tab仍然未关闭,切换了另一tab,然后回去刚才的tab,发现已经close的dialog又 ...

  6. scrapy基础知识之 parse()方法的工作机制思考:

    1.因为使用的yield,而不是return.parse函数将会被当做一个生成器使用.scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型: 2.如果是request则加入 ...

  7. 安卓学习资料推荐《深入理解Android:卷2》下载

    下载地址:百度云下载地址 编辑推荐 <深入理解Android:卷2>编辑推荐:经典畅销书<深入理解Android:卷I>姊妹篇,51CTO移动开发频道和开源中国社区一致鼎力推荐 ...

  8. android_aidl

    好久未更新博客了.人都是这样,刚开始对某一样东东冲劲十足,时间一长,很难坚持下去了,我这博客也是.所以我要打破成规,继续更新. 本次博客谈谈adil的用法.aidl的全称叫什么来着忘了,不过不要紧,重 ...

  9. Vue根据不同的路由文件实现打包差异化

    有些时候我们经常一个项目中开发不同的功能,有可能一个前端项目中夹杂着不同系统之间的需求,最后打包发布的时候经常会将与项目不相关的代码一同打包进去,实际来讲这种操作也是不严谨的.那有没有办法可以根据某些 ...

  10. 从后端到前端之Vue(二)写个tab试试水

    上一篇写了一下table,然后要写什么呢?当然是tab了.动态创建一个tab,里面放一个table,这样一个后台管理的基本功能(之一)就出来了. 好吧,这里其实只是试试水,感受一下vue的数据驱动可以 ...