• GStreamer基础教程06


    摘要

    在常见的媒体文件中,通常包含一些数据(例如:歌手,专辑,编码类型等),用于描述媒体文件。通常称这些数据为元数据(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 ("	%20s : %s
    ", tag, g_value_get_string (val));
        } else if (G_VALUE_HOLDS_UINT (val)) {
          g_print ("	%20s : %u
    ", tag, g_value_get_uint (val));
        } else if (G_VALUE_HOLDS_DOUBLE (val)) {
          g_print ("	%20s : %g
    ", tag, g_value_get_double (val));
        } else if (G_VALUE_HOLDS_BOOLEAN (val)) {
          g_print ("	%20s : %s
    ", 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 ("	%20s : buffer of size %u
    ", 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 ("	%20s : %s
    ", tag, dt_str);
          g_free (dt_str);
        } else {
          g_print ("	%20s : tag of type '%s'
    ", 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:
    ", GST_OBJECT_NAME (msg->src));
        gst_tag_list_foreach (tags, print_one_tag, NULL);
        g_print ("
    ");
        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
    ", 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 ("	%20s : %s
    ", 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:
    ", GST_OBJECT_NAME (msg->src));
        gst_tag_list_foreach (tags, print_one_tag, NULL);
        g_print ("
    ");
        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
    本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.
  • 相关阅读:
    uva 147 Dollars
    hdu 2069 Coin Change(完全背包)
    hdu 1708 Fibonacci String
    hdu 1568 Fibonacci
    hdu 1316 How Many Fibs?
    poj 1958 Strange Towers of Hanoi
    poj 3601Tower of Hanoi
    poj 3572 Hanoi Tower
    poj 1920 Towers of Hanoi
    筛选法——素数打表
  • 原文地址:https://www.cnblogs.com/xleng/p/11277397.html
Copyright © 2020-2023  润新知