Gstreamer Basic Tutorial 9: media information collection

1.Goal

Sometimes you may want to quickly find out which media the file (or URI) contains, or whether you can play all the media. You can build a pipeline, set it up to run, and view bus messages, but GStreamer has a utility to do this for you. This tutorial shows:

  • How to recover information about URI s
  • How do I determine if a URI is playable

2. Introduction

GstDiscoverer is a utility object found in the pbutils Library (plug-in base utility), which accepts URIs or lists of URIs and returns information about them. It can work in synchronous or asynchronous mode.
In synchronous mode, there is only one function GST to call_ discoverer_ discover_ The URI blocks until the message is ready. Because of this blocking, it is usually less interesting for GUI based applications and uses asynchronous mode, as described in this tutorial.
The recovered information includes codec description, stream topology (number of streams and sub streams) and available metadata (such as audio language).
For example, this is https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm Results.
It is suggested to save the picture directly from the external chain (s814ffa785, s814ffa2608)
The following code attempts to discover the URI provided through the command line and outputs the retrieved information (if no URI is provided, the default value is used).
This is a simplified version of the gst-discoverer-1.0 tool (basic tutorial 10: GStreamer tool), which only displays data and does not perform any playback.

3.The GStreamer Discoverer

3.1 Compile

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

3.2 Code

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

4. Analysis

These are the main steps in using 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 creates a new Discoverer object. The first parameter is the timeout for each file, in nanoseconds (for simplicity, use the GST_SECOND macro).

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

Link the signal of interest.

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

gst_discoverer_start starts the discovery process, but we haven't provided any URI s to discover. 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 puts the URI provided into the queue for discovery. You can use this feature to queue multiple URIs. When their discovery process is completed, the registered callback function will be triggered.

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

The usual GLib main loop is instantiated and executed. When from on_finished_cb callback call G_ main_ loop_ When quit, we will exit the loop.

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

After the discoverer completes the operation, use gst_discoverer_stop stops it and uses g_object_unref dereferences it.
Now let's review our registered callback on_discovered_cb:

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

When Discoverer has finished processing a URI, this function is called and provides us with a GstDiscovererInfo structure containing all the information.
The first step is to use gst_discoverer_info_get_uri retrieves the specific URI referenced by this call (sometimes multiple discovery processes run, which is not the case in this example), and uses gst_discoverer_info_get_result() retrieves the found results.

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

As shown in the code, GST_ DISCOVERER_ Any result other than OK means that there is a problem and the URI cannot be played. The reasons may vary, but the enumeration value is very clear (gst_discover_busy occurs only in synchronous mode and is not used in this example).
If no errors occur, you can use a different GST_ discoverer_ info_ get_* Methods (for example, gst_discoverer_info_get_duration) retrieve information from the GstDiscovererInfo structure.
Information bits composed of lists, such as tags and stream info, require some additional 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 are metadata (tags) attached to the media. You can use gst_tag_list_foreach checks them, and the function calls print for each tag found_ tag_ Foreach (for example, you can also traverse the list manually, or you can use gst_tag_list_get_string to search for a specific tag). print_tag_foreach's code is almost self-evident.

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 returns a gtdiscovererstreaminfo structure, which is in print_ It is parsed in the topology function and then gst_discoverer_stream_info_unref discard.

/* 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_ The code of the info function is also self-evident: it also uses print_tag_foreach print stream function, and then print the relevant caps.
Then, print_topology looks for the next element to display. If GST_ discoverer_ stream_ info_ get_ If next returns a non NULL stream information, it points to our descendants and should display this information. Otherwise, if we are a container, we pass GST_ discoverer_ container_ info_ get_ Each descendant obtained by streams recursively calls print_topology. Otherwise, we are the final stream and do not need recursion (this part of the recognized Discoverer API is somewhat obscure).

Ref

  • gst_discoverer_new
GstDiscoverer *gst_discoverer_new(GstClockTime timout, GError **error))

Create a GstDiscoverer with timeout.
timeout is in nanoseconds and the range is 1s1h(GST_SECOND3600*GST_SECOND)

  • gst_discoverer_start
  • gst_discoverer_discover_uri_async
  • gst_discoverer_info_get_uri
  • gst_discoverer_info_get_result
  • gst-discoverer_info_get_misc
  • gst_structure_to_string
  • gst_discoverer_info_get_*
  • gst_tag_list_foreach
  • gst_discoverer_stream_info_get_next
  • gst_discoverer_container_info_get_streams

5. Discussion

This tutorial shows:

  • How to use GstDiscoverer to recover information about URI s
  • How to use GST by viewing_ discoverer_ info_ get_ Result to determine whether the URI can be played

Keywords: C GStreamer

Added by btrsrinivasarao on Tue, 01 Feb 2022 15:53:36 +0200