gstreamer基础教程9-Media information gathering

官网:https://gstreamer.freedesktop.org/

Demo基础教程:https://gstreamer.freedesktop.org/documentation/tutorials/basic/concepts.html

Demo下载地址:git://anongit.freedesktop.org/gstreamer/gst-docs

Goal

有时候我们需要快速知道一个文件或者一个URI包含多少个流,这章将要告诉你如何快速获取流信息以及判断流是否可以播放。

Sometimes you might want to quickly find out what kind of media a file (or URI) contains, or if you will be able to play the media at all. You can build a pipeline, set it to run, and watch the bus messages, but GStreamer has a utility that does just that for you. This tutorial shows:

  • How to recover重新获取 information regarding a URI

  • How to find out if a URI is playable

Introduction

GstDiscoverer 是一个非常好用的工具,接收一个或多个URI,返回它们的信息。可以同步可以异步。同步情况下只需要调用gst_discoverer_discover_uri()即可。

GstDiscoverer is a utility object found in the pbutils library (Plug-in Base utilities) that accepts a URI or list of URIs, and returns information about them. It can work in synchronous or asynchronous modes.

In synchronous mode, there is only a single function to call, gst_discoverer_discover_uri(), which blocks until the information is ready. Due to this blocking, it is usually less interesting for GUI-based applications and the asynchronous mode is used, as described in this tutorial.

The recovered information includes codec descriptions, stream topology拓扑 (number of streams and sub-streams) and available metadata元数据 (like the audio language).

As an example, this is the result of discovering 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工具的简化版本,仅仅暂时数据不进行回放。

The following code tries to discover the URI provided through the command line, and outputs the retrieved information (If no URI is provided it uses a default one).

This is a simplified version of what the gst-discoverer-1.0 tool does (Basic tutorial 10: GStreamer tools), which is an application that only displays data, but does not perform any playback.

The GStreamer Discoverer

Copy this code into a text file named basic-tutorial-9.c (or find it in your GStreamer installation).

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 cumstom 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;
}

这个例子主要是打开一个URI,然后暂时URI的信息。如果是网络资源,响应会慢一点。

This tutorial opens the URI passed as the first parameter in the command line (or a default URI if none is provided) and outputs information about it on the screen. If the media is located on the Internet, the application might take a bit to react反应 depending on your connection speed.

Walkthrough

These are the main steps to use the 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()创建一个查找实例,第一个参数是超时时间(单位是纳秒),第二个参数是错误引用。

gst_discoverer_new() creates a new Discoverer object. The first parameter is the timeout per file, in nanoseconds (use the GST_SECOND macro for simplicity).

/* 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);

关联感兴趣的消息,具体在回调片段介绍

Connect to the interesting signals, as usual. We discuss them in the snippet片段 for their callbacks.

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

gst_discoverer_start()设置开始标志位,实际没有开始。

gst_discoverer_start() launches the discovering process, but we have not provided any URI to discover yet. This is done next:

/* 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()提供URL给发现队列,多次调用此函数可以插入多个URL。随着discovery的处理完成,他们会触发discovered消息,通知回调函数。

gst_discoverer_discover_uri_async() enqueues the provided URI for discovery. Multiple URIs can be enqueued with this function. As the discovery process for each of them finishes, the registered callback functions will be fired up.

/* 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);

创建Gib主循环,然后执行,在on_finished_cb回调中将会被关闭。

The usual GLib main loop is instantiated and executed. We will get out of it when g_main_loop_quit() is called from the on_finished_cb callback.

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

完成信息检索后,需要通过gst_discoverer_stop()停止检索(对应gst_discoverer_start()),并且用 g_object_unref()减除索引。

Once we are done with the discoverer, we stop it with gst_discoverer_stop() and unref it with g_object_unref().

Let's review now the callbacks we have registered:

/* 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);

当检索完成后,此回调被触发,返回一个GstDiscovererInfo结构。gst_discoverer_info_get_uri() 查询返回的是哪个URI的结果。 gst_discoverer_info_get_result()返回结果的状态。状态结果将下段代码(错误,超时等待)。

We got here because the Discoverer has finished working on one URI, and provides us a GstDiscovererInfo structure with all the information.

The first step is to retrieve the particular URI this call refers to (in case we had multiple discover process running, which is not the case in this example) with gst_discoverer_info_get_uri() and the discovery result with 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 ,其他值都是错误的。具体原因已经很明确了。GST_DISCOVERER_BUSY只有在同步的情况下会出现。如果成功的话,可以通过一系列函数gst_discoverer_info_get_* 获取,比如gst_discoverer_info_get_duration()等等。但是一些列表信息,需要额外解析。比如流信息,标签等。

As the code shows, any result other than GST_DISCOVERER_OK means that there has been some kind of problem, and this URI cannot be played. The reasons can vary, but the enum values are quite explicit明确 (GST_DISCOVERER_BUSY can only happen when in synchronous 同步mode, which is not used in this example).

If no error happened, information can be retrieved from the GstDiscovererInfo structure with the different gst_discoverer_info_get_* methods (like, gst_discoverer_info_get_duration(), for example).

Bits of information(信息) which are made of lists, like tags and stream info, needs some extra parsing:

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));
}

tags是一个媒体的标签,可以通过gst_tag_list_foreach()获取,第二个参数类似于回调,多次调用。这里多次调用print_tag_foreach函数,将tass打印出来。当然也可以不使用gst_tag_list_foreach()手动获取gst_tag_list_get_string(),以字符串形式返回所有tags信息。

Tags are metadata (labels) attached to the media. They can be examined检查 with gst_tag_list_foreach(), which will call print_tag_foreach for each tag found (the list could also be traversed manually, for example, or a specific tag could be searched for with gst_tag_list_get_string()). The code for print_tag_foreach is pretty much self-explicative阐明.

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() 获取info信息,返回GstDiscovererStreamInfo结构体。最后使用gst_discoverer_stream_info_unref()结构体释放info。print_topology 函数打印info内容。

gst_discoverer_info_get_stream_info() returns a GstDiscovererStreamInfo structure that is parsed in the print_topology function, and then discarded with 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()获取下一个info,知道完成递归。

The print_stream_info function's code is also pretty much self-explicative: it prints the stream's capabilities and then the associated caps, using print_tag_foreach too.

Then, print_topology looks for the next element to display. If gst_discoverer_stream_info_get_next() returns a non-NULL stream info, it refers to our descendant and that should be displayed. Otherwise, if we are a container容器, recursively递归 call print_topology on each of our children obatined with gst_discoverer_container_info_get_streams(). Otherwise, we are a final stream, and do not need to recurse (This part of the Discoverer API is admittedly公认的 a bit obscure难懂).

Conclusion

This tutorial has shown:

  • How to recover information regarding a URI using the GstDiscoverer

  • How to find out if a URI is playable by looking at the return code obtained with gst_discoverer_info_get_result().

It has been a pleasure having you here, and see you soon!

猜你喜欢

转载自blog.csdn.net/knowledgebao/article/details/82787678