GStreamer基础教程06 – 获取媒体信息

  • 2019 年 10 月 3 日
  • 筆記

摘要

在常见的媒体文件中,通常包含一些数据(例如:歌手,专辑,编码类型等),用于描述媒体文件。通常称这些数据为元数据(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 : %sn", tag, g_value_get_string (val));      } else if (G_VALUE_HOLDS_UINT (val)) {        g_print ("t%20s : %un", tag, g_value_get_uint (val));      } else if (G_VALUE_HOLDS_DOUBLE (val)) {        g_print ("t%20s : %gn", tag, g_value_get_double (val));      } else if (G_VALUE_HOLDS_BOOLEAN (val)) {        g_print ("t%20s : %sn", 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 %un", 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 : %sn", 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: %sn", 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 : %sn", 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

 

作者:John.Leng
本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.