GStreamer Basic tutorial 9 Media information gathering

"GStreamer 基本教程9: 媒体信息收集"

Posted by Stephen on March 21, 2022

前言

本文是GStreamer学习笔记,也可以看成是对原文的意译。

这些教程描述了理解其余教程所需的GStreamer主题。

GStreamer教程:

基础教程 : GStreamer 介绍

基础教程 1: Hello world!

基础教程 2: GStreamer 概念

基础教程 3: 动态管道

基础教程 4: 时间管理

基础教程 5: GUI工具包集成

基础教程 6: 媒体格式和pad功能

基础教程 7: 多线程和Pad可用性

基础教程 8: 管道短路操作

基础教程 9: 媒体信息收集

基础教程 10: GStreamer工具

基础教程 11: 调试工具

基础教程 12: 流媒体

基础教程 13: 播放速度

基础教程 14: 有用的元素

基础教程 16: 特定平台元素

环境

系统环境

Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.4 LTS
Release:	18.04
Codename:	bionic
Linux version :       5.3.0-46-generic ( buildd@lcy01-amd64-013 ) 
Gcc version:         7.5.0  ( Ubuntu 7.5.0-3ubuntu1~18.04 )

软件信息

version : 	
     GStreamer-1.0

正文

1. 目标

有时您可能想快速找出文件(或 URI)包含的媒体类型,或者您是否能够播放该媒体。您可以构建管道,将其设置为运行,并查看总线消息,但 GStreamer 有一个实用程序可以为您完成这些工作。本教程显示:

  • 如何恢复有关 URI 的信息
  • 如何确定 URI 是否可播放

2. 介绍

GstDiscoverer是在pbutils库(插件基础实用程序)中找到的实用程序对象,它接受 URI 或 URI 列表,并返回有关它们的信息。它可以在同步或异步模式下工作。

在同步模式下,只有一个函数可以调用, gst_discoverer_discover_uri()该函数会一直阻塞,直到信息准备好。由于这种阻塞,基于 GUI 的应用程序通常不太有趣,并且使用异步模式,如本教程中所述。

恢复的信息包括编解码器描述、流拓扑(流和子流的数量)和可用的元数据(如音频语言)。

例如,这是发现 https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm 的结果

   Duration: 0:00:52.250000000
   Tags:
     video codec: On2 VP8
     language code: en
     container format: Matroska
     application name: ffmpeg2theora-0.24
     encoder: Xiph.Org libVorbis I 20090709
     encoder version: 0
     audio codec: Vorbis
     nominal bitrate: 80000
     bitrate: 80000
   Seekable: yes
   Stream information:
     container: WebM
       audio: Vorbis
         Tags:
           language code: en
           container format: Matroska
           audio codec: Vorbis
           application name: ffmpeg2theora-0.24
           encoder: Xiph.Org libVorbis I 20090709
           encoder version: 0
           nominal bitrate: 80000
           bitrate: 80000
       video: VP8
         Tags:
           video codec: VP8 video
           container format: Matroska

以下代码尝试发现通过命令行提供的 URI,并输出检索到的信息(如果未提供 URI,则使用默认值)。

这是该gst-discoverer-1.0工具的简化版本(基础教程 10: GStreamer工具),它是一个仅显示数据但不执行任何回放的应用程序。

3. GStreamer发现者

basic-tutorial-9.c

   #include <string.h>
   #include <gst/gst.h>
   #include <gst/pbutils/pbutils.h>
   
   /* Structure to contain all our information, so we can pass it around */
   typedef struct _CustomData {
     GstDiscoverer *discoverer;
     GMainLoop *loop;
   } CustomData;
   
   /* Print a tag in a human-readable format (name: value) */
   static void print_tag_foreach (const GstTagList *tags, const gchar *tag, gpointer user_data) {
     GValue val = { 0, };
     gchar *str;
     gint depth = GPOINTER_TO_INT (user_data);
   
     gst_tag_list_copy_value (&val, tags, tag);
   
     if (G_VALUE_HOLDS_STRING (&val))
       str = g_value_dup_string (&val);
     else
       str = gst_value_serialize (&val);
   
     g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
     g_free (str);
   
     g_value_unset (&val);
   }
   
   /* Print information regarding a stream */
   static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) {
     gchar *desc = NULL;
     GstCaps *caps;
     const GstTagList *tags;
   
     caps = gst_discoverer_stream_info_get_caps (info);
   
     if (caps) {
       if (gst_caps_is_fixed (caps))
         desc = gst_pb_utils_get_codec_description (caps);
       else
         desc = gst_caps_to_string (caps);
       gst_caps_unref (caps);
     }
   
     g_print ("%*s%s: %s\n", 2 * depth, " ", gst_discoverer_stream_info_get_stream_type_nick (info), (desc ? desc : ""));
   
     if (desc) {
       g_free (desc);
       desc = NULL;
     }
   
     tags = gst_discoverer_stream_info_get_tags (info);
     if (tags) {
       g_print ("%*sTags:\n", 2 * (depth + 1), " ");
       gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (depth + 2));
     }
   }
   
   /* Print information regarding a stream and its substreams, if any */
   static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
     GstDiscovererStreamInfo *next;
   
     if (!info)
       return;
   
     print_stream_info (info, depth);
   
     next = gst_discoverer_stream_info_get_next (info);
     if (next) {
       print_topology (next, depth + 1);
       gst_discoverer_stream_info_unref (next);
     } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
       GList *tmp, *streams;
   
       streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));
       for (tmp = streams; tmp; tmp = tmp->next) {
         GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
         print_topology (tmpinf, depth + 1);
       }
       gst_discoverer_stream_info_list_free (streams);
     }
   }
   
   /* This function is called every time the discoverer has information regarding
    * one of the URIs we provided.*/
   static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {
     GstDiscovererResult result;
     const gchar *uri;
     const GstTagList *tags;
     GstDiscovererStreamInfo *sinfo;
   
     uri = gst_discoverer_info_get_uri (info);
     result = gst_discoverer_info_get_result (info);
     switch (result) {
       case GST_DISCOVERER_URI_INVALID:
         g_print ("Invalid URI '%s'\n", uri);
         break;
       case GST_DISCOVERER_ERROR:
         g_print ("Discoverer error: %s\n", err->message);
         break;
       case GST_DISCOVERER_TIMEOUT:
         g_print ("Timeout\n");
         break;
       case GST_DISCOVERER_BUSY:
         g_print ("Busy\n");
         break;
       case GST_DISCOVERER_MISSING_PLUGINS:{
         const GstStructure *s;
         gchar *str;
   
         s = gst_discoverer_info_get_misc (info);
         str = gst_structure_to_string (s);
   
         g_print ("Missing plugins: %s\n", str);
         g_free (str);
         break;
       }
       case GST_DISCOVERER_OK:
         g_print ("Discovered '%s'\n", uri);
         break;
     }
   
     if (result != GST_DISCOVERER_OK) {
       g_printerr ("This URI cannot be played\n");
       return;
     }
   
     /* If we got no error, show the retrieved information */
   
     g_print ("\nDuration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_discoverer_info_get_duration (info)));
   
     tags = gst_discoverer_info_get_tags (info);
     if (tags) {
       g_print ("Tags:\n");
       gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
     }
   
     g_print ("Seekable: %s\n", (gst_discoverer_info_get_seekable (info) ? "yes" : "no"));
   
     g_print ("\n");
   
     sinfo = gst_discoverer_info_get_stream_info (info);
     if (!sinfo)
       return;
   
     g_print ("Stream information:\n");
   
     print_topology (sinfo, 1);
   
     gst_discoverer_stream_info_unref (sinfo);
   
     g_print ("\n");
   }
   
   /* This function is called when the discoverer has finished examining
    * all the URIs we provided.*/
   static void on_finished_cb (GstDiscoverer *discoverer, CustomData *data) {
     g_print ("Finished discovering\n");
   
     g_main_loop_quit (data->loop);
   }
   
   int main (int argc, char **argv) {
     CustomData data;
     GError *err = NULL;
     gchar *uri = "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm";
   
     /* if a URI was provided, use it instead of the default one */
     if (argc > 1) {
       uri = argv[1];
     }
   
     /* Initialize custom data structure */
     memset (&data, 0, sizeof (data));
   
     /* Initialize GStreamer */
     gst_init (&argc, &argv);
   
     g_print ("Discovering '%s'\n", uri);
   
     /* Instantiate the Discoverer */
     data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
     if (!data.discoverer) {
       g_print ("Error creating discoverer instance: %s\n", err->message);
       g_clear_error (&err);
       return -1;
     }
   
     /* Connect to the interesting signals */
     g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);
     g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);
   
     /* Start the discoverer process (nothing to do yet) */
     gst_discoverer_start (data.discoverer);
   
     /* Add a request to process asynchronously the URI passed through the command line */
     if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {
       g_print ("Failed to start discovering URI '%s'\n", uri);
       g_object_unref (data.discoverer);
       return -1;
     }
   
     /* Create a GLib Main Loop and set it to run, so we can wait for the signals */
     data.loop = g_main_loop_new (NULL, FALSE);
     g_main_loop_run (data.loop);
   
     /* Stop the discoverer process */
     gst_discoverer_stop (data.discoverer);
   
     /* Free resources */
     g_object_unref (data.discoverer);
     g_main_loop_unref (data.loop);
   
     return 0;
   }

编译运行

   gcc basic-tutorial-9.c -o basic-tutorial-9 `pkg-config --cflags --libs gstreamer-1.0 gstreamer-pbutils-1.0`
   ./basic-tutorial-9

4. 演练

这些是使用的主要步骤GstDiscoverer

   /* Instantiate the Discoverer */
   data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
   if (!data.discoverer) {
     g_print ("Error creating discoverer instance: %s\n", err->message);
     g_clear_error (&err);
     return -1;
   }

gst_discoverer_new()创建一个新的 Discoverer 对象。第一个参数是每个文件的超时时间,以纳秒为单位( GST_SECOND为简单起见,使用宏)。

   /* Connect to the interesting signals */
   g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);
   g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);

像往常一样连接到有趣的信号。我们在片段中讨论它们的回调。

   /* Start the discoverer process (nothing to do yet) */
   gst_discoverer_start (data.discoverer);

gst_discoverer_start()启动发现过程,但我们尚未提供任何要发现的 URI。这是接下来完成的:

   /* Add a request to process asynchronously the URI passed through the command line */
   if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {
     g_print ("Failed to start discovering URI '%s'\n", uri);
     g_object_unref (data.discoverer);
     return -1;
   }

gst_discoverer_discover_uri_async()将提供的 URI 排入队列以进行发现。可以使用此函数将多个 URI 加入队列。随着他们每个人的发现过程完成,注册的回调函数将被启动。

   /* Create a GLib Main Loop and set it to run, so we can wait for the signals */
   data.loop = g_main_loop_new (NULL, FALSE);
   g_main_loop_run (data.loop);

通常的 GLib 主循环被实例化并执行。g_main_loop_quit()当从 on_finished_cb回调中调用时,我们将摆脱它。

   /* Stop the discoverer process */
   gst_discoverer_stop (data.discoverer);

一旦我们完成了发现者,我们用 停止它 gst_discoverer_stop()并用 取消引用它g_object_unref()

现在让我们回顾一下我们注册的回调:

   /* This function is called every time the discoverer has information regarding
    * one of the URIs we provided.*/
   static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {
     GstDiscovererResult result;
     const gchar *uri;
     const GstTagList *tags;
     GstDiscovererStreamInfo *sinfo;
   
     uri = gst_discoverer_info_get_uri (info);
     result = gst_discoverer_info_get_result (info);

我们来到这里是因为 Discoverer 已经完成了对一个 URI 的处理,并为我们提供了一个GstDiscovererInfo包含所有信息的结构。

第一步是检索这个调用引用的特定 URI(如果我们有多个发现进程正在运行,在这个例子中不是这种情况)gst_discoverer_info_get_uri()和发现结果gst_discoverer_info_get_result()

   switch (result) {
     case GST_DISCOVERER_URI_INVALID:
       g_print ("Invalid URI '%s'\n", uri);
       break;
     case GST_DISCOVERER_ERROR:
       g_print ("Discoverer error: %s\n", err->message);
       break;
     case GST_DISCOVERER_TIMEOUT:
       g_print ("Timeout\n");
       break;
     case GST_DISCOVERER_BUSY:
       g_print ("Busy\n");
       break;
     case GST_DISCOVERER_MISSING_PLUGINS:{
       const GstStructure *s;
       gchar *str;
   
       s = gst_discoverer_info_get_misc (info);
       str = gst_structure_to_string (s);
   
       g_print ("Missing plugins: %s\n", str);
       g_free (str);
       break;
     }
     case GST_DISCOVERER_OK:
       g_print ("Discovered '%s'\n", uri);
       break;
   }
   
   if (result != GST_DISCOVERER_OK) {
     g_printerr ("This URI cannot be played\n");
     return;
   }

如代码所示,除此之外的任何结果GST_DISCOVERER_OK都意味着出现了某种问题,并且无法播放此 URI。原因可能会有所不同,但枚举值非常明确(GST_DISCOVERER_BUSY只能在同步模式下发生,本示例中未使用)。

如果没有发生错误,则可以 使用不同的方法(例如, 例如)从GstDiscovererInfo结构 中检索信息。gst_discoverer_info_get_*gst_discoverer_info_get_duration()

由列表组成的信息位,如标签和流信息,需要一些额外的解析:

   tags = gst_discoverer_info_get_tags (info);
   if (tags) {
     g_print ("Tags:\n");
     gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
   }

标签是附加到媒体的元数据(标签)。可以使用 来检查它们gst_tag_list_foreach(),这将调用print_tag_foreach找到的每个标签(例如,也可以手动遍历列表,或者可以使用 搜索特定标签 gst_tag_list_get_string())。的代码print_tag_foreach几乎是不言自明的。

   sinfo = gst_discoverer_info_get_stream_info (info);
   if (!sinfo)
     return;
   
   g_print ("Stream information:\n");
   
   print_topology (sinfo, 1);
   
   gst_discoverer_stream_info_unref (sinfo);

gst_discoverer_info_get_stream_info()返回GstDiscovererStreamInfo在函数中解析的结构,print_topology然后用 . 丢弃gst_discoverer_stream_info_unref()

   /* Print information regarding a stream and its substreams, if any */
   static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
     GstDiscovererStreamInfo *next;
   
     if (!info)
       return;
   
     print_stream_info (info, depth);
   
     next = gst_discoverer_stream_info_get_next (info);
     if (next) {
       print_topology (next, depth + 1);
       gst_discoverer_stream_info_unref (next);
     } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
       GList *tmp, *streams;
   
       streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));
       for (tmp = streams; tmp; tmp = tmp->next) {
         GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
         print_topology (tmpinf, depth + 1);
       }
       gst_discoverer_stream_info_list_free (streams);
     }
   }

print_stream_info函数的代码也几乎不言自明:它打印流的功能,然后打印相关的上限,print_tag_foreach也使用。

然后,print_topology寻找下一个要显示的元素。如果 gst_discoverer_stream_info_get_next()返回非 NULL 流信息,它指的是我们的后代,应该显示。否则,如果我们是一个容器,递归调用print_topology每个通过 获得的孩子gst_discoverer_container_info_get_streams()。否则,我们是一个最终流,不需要递归(Discoverer API 的这一部分确实有点晦涩难懂)。

5. 结论

本教程显示:

  • 如何使用GstDiscoverer
  • 如何通过查看使用gst_discoverer_info_get_result().

后记