GStreamer基础教程09——收集媒体信息

目标

      有时你需要快速的了解一个文件(或URI)包含的媒体格式或者看看是否支持这种格式。当然你可以创建一个pipeline,设置运行,观察总线上的消息,但GStreamer提供了一个工具可以帮你做这些。本教程主要讲述:

      如何获得一个URI上的信息

      如何确定一个URI是可以播放的


介绍

      GstDiscover是一个在pbutils库提供的工具,接受输入URI或者URI列表,返回它们的信息。这个工具可以工作在同步或者异步模式下。

      在同步模式下,只有一个API可以用,就是gst_discoverer_discover_uri(),这个API会阻塞线程直到得到需要的信息。因为阻塞会带来GUI上很糟糕的体验,所以基于UI的应用主要使用异步模式,本教程也是基于异步模式的。

      检测到的信息会包括codec的描述,流的拓扑结构和可以获得的元数据。


[html]  view plain  copy
 
  1. <span style="font-size:14px;">As an example, this is the result of discovering http://docs.gstreamer.com/media/sintel_trailer-480p.webm (Click to expand)  
  2. Duration: 0:00:52.250000000  
  3. Tags:  
  4.   video codec: On2 VP8  
  5.   language code: en  
  6.   container format: Matroska  
  7.   application name: ffmpeg2theora-0.24  
  8.   encoder: Xiph.Org libVorbis I 20090709  
  9.   encoder version: 0  
  10.   audio codec: Vorbis  
  11.   nominal bitrate: 80000  
  12.   bitrate: 80000  
  13. Seekable: yes  
  14. Stream information:  
  15.   container: WebM  
  16.     audio: Vorbis  
  17.       Tags:  
  18.         language code: en  
  19.         container format: Matroska  
  20.         audio codec: Vorbis  
  21.         application name: ffmpeg2theora-0.24  
  22.         encoder: Xiph.Org libVorbis I 20090709  
  23.         encoder version: 0  
  24.         nominal bitrate: 80000  
  25.         bitrate: 80000  
  26.     video: VP8  
  27.       Tags:  
  28.         video codec: VP8 video  
  29.         container format: Matroska</span>  
      下面的代码试着通过命令行的方式来运行discover工具来解析URI的内容,输出获得的信息。


      这是gst-discoverer工具使用的简单版本,这个应用仅仅显示数据,甚至不提供播放功能。


GStreamer Discoverer


[html]  view plain  copy
 
  1. <span style="font-size:14px;">#include <string.h>  
  2. #include <gst/gst.h>  
  3. #include <gst/pbutils/pbutils.h>  
  4.     
  5. /* Structure to contain all our information, so we can pass it around */  
  6. typedef struct _CustomData {  
  7.   GstDiscoverer *discoverer;  
  8.   GMainLoop *loop;  
  9. } CustomData;  
  10.     
  11. /* Print a tag in a human-readable format (name: value) */  
  12. static void print_tag_foreach (const GstTagList *tags, const gchar *tag, gpointer user_data) {  
  13.   GValue val = { 0, };  
  14.   gchar *str;  
  15.   gint depth = GPOINTER_TO_INT (user_data);  
  16.     
  17.   gst_tag_list_copy_value (&val, tags, tag);  
  18.     
  19.   if (G_VALUE_HOLDS_STRING (&val))  
  20.     str = g_value_dup_string (&val);  
  21.   else  
  22.     str = gst_value_serialize (&val);  
  23.     
  24.   g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);  
  25.   g_free (str);  
  26.     
  27.   g_value_unset (&val);  
  28. }  
  29.     
  30. /* Print information regarding a stream */  
  31. static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) {  
  32.   gchar *desc = NULL;  
  33.   GstCaps *caps;  
  34.   const GstTagList *tags;  
  35.     
  36.   caps = gst_discoverer_stream_info_get_caps (info);  
  37.     
  38.   if (caps) {  
  39.     if (gst_caps_is_fixed (caps))  
  40.       desc = gst_pb_utils_get_codec_description (caps);  
  41.     else  
  42.       desc = gst_caps_to_string (caps);  
  43.     gst_caps_unref (caps);  
  44.   }  
  45.     
  46.   g_print ("%*s%s: %s\n", 2 * depth, " ", gst_discoverer_stream_info_get_stream_type_nick (info), (desc ? desc : ""));  
  47.     
  48.   if (desc) {  
  49.     g_free (desc);  
  50.     desc = NULL;  
  51.   }  
  52.     
  53.   tags = gst_discoverer_stream_info_get_tags (info);  
  54.   if (tags) {  
  55.     g_print ("%*sTags:\n", 2 * (depth + 1), " ");  
  56.     gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (depth + 2));  
  57.   }  
  58. }  
  59.     
  60. /* Print information regarding a stream and its substreams, if any */  
  61. static void print_topology (GstDiscovererStreamInfo *info, gint depth) {  
  62.   GstDiscovererStreamInfo *next;  
  63.     
  64.   if (!info)  
  65.     return;  
  66.     
  67.   print_stream_info (info, depth);  
  68.     
  69.   next = gst_discoverer_stream_info_get_next (info);  
  70.   if (next) {  
  71.     print_topology (next, depth + 1);  
  72.     gst_discoverer_stream_info_unref (next);  
  73.   } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {  
  74.     GList *tmp, *streams;  
  75.       
  76.     streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));  
  77.     for (tmp = streams; tmp; tmp = tmp->next) {  
  78.       GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;  
  79.       print_topology (tmpinf, depth + 1);  
  80.     }  
  81.     gst_discoverer_stream_info_list_free (streams);  
  82.   }  
  83. }  
  84.     
  85. /* This function is called every time the discoverer has information regarding  
  86.  * one of the URIs we provided.*/  
  87. static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {  
  88.   GstDiscovererResult result;  
  89.   const gchar *uri;  
  90.   const GstTagList *tags;  
  91.   GstDiscovererStreamInfo *sinfo;  
  92.     
  93.   uri = gst_discoverer_info_get_uri (info);  
  94.   result = gst_discoverer_info_get_result (info);  
  95.   switch (result) {  
  96.     case GST_DISCOVERER_URI_INVALID:  
  97.       g_print ("Invalid URI '%s'\n", uri);  
  98.       break;  
  99.     case GST_DISCOVERER_ERROR:  
  100.       g_print ("Discoverer error: %s\n", err->message);  
  101.       break;  
  102.     case GST_DISCOVERER_TIMEOUT:  
  103.       g_print ("Timeout\n");  
  104.       break;  
  105.     case GST_DISCOVERER_BUSY:  
  106.       g_print ("Busy\n");  
  107.       break;  
  108.     case GST_DISCOVERER_MISSING_PLUGINS:{  
  109.       const GstStructure *s;  
  110.       gchar *str;  
  111.         
  112.       s = gst_discoverer_info_get_misc (info);  
  113.       str = gst_structure_to_string (s);  
  114.         
  115.       g_print ("Missing plugins: %s\n", str);  
  116.       g_free (str);  
  117.       break;  
  118.     }  
  119.     case GST_DISCOVERER_OK:  
  120.       g_print ("Discovered '%s'\n", uri);  
  121.       break;  
  122.   }  
  123.     
  124.   if (result != GST_DISCOVERER_OK) {  
  125.     g_printerr ("This URI cannot be played\n");  
  126.     return;  
  127.   }  
  128.     
  129.   /* If we got no error, show the retrieved information */  
  130.     
  131.   g_print ("\nDuration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_discoverer_info_get_duration (info)));  
  132.     
  133.   tags = gst_discoverer_info_get_tags (info);  
  134.   if (tags) {  
  135.     g_print ("Tags:\n");  
  136.     gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));  
  137.   }  
  138.     
  139.   g_print ("Seekable: %s\n", (gst_discoverer_info_get_seekable (info) ? "yes" : "no"));  
  140.     
  141.   g_print ("\n");  
  142.     
  143.   sinfo = gst_discoverer_info_get_stream_info (info);  
  144.   if (!sinfo)  
  145.     return;  
  146.     
  147.   g_print ("Stream information:\n");  
  148.     
  149.   print_topology (sinfo, 1);  
  150.     
  151.   gst_discoverer_stream_info_unref (sinfo);  
  152.     
  153.   g_print ("\n");  
  154. }  
  155.     
  156. /* This function is called when the discoverer has finished examining  
  157.  * all the URIs we provided.*/  
  158. static void on_finished_cb (GstDiscoverer *discoverer, CustomData *data) {  
  159.   g_print ("Finished discovering\n");  
  160.     
  161.   g_main_loop_quit (data->loop);  
  162. }  
  163.     
  164. int main (int argc, char **argv) {  
  165.   CustomData data;  
  166.   GError *err = NULL;  
  167.   gchar *uri = "http://docs.gstreamer.com/media/sintel_trailer-480p.webm";  
  168.     
  169.   /* if a URI was provided, use it instead of the default one */  
  170.   if (argc > 1) {  
  171.     uri = argv[1];  
  172.   }  
  173.     
  174.   /* Initialize cumstom data structure */  
  175.   memset (&data, 0, sizeof (data));  
  176.     
  177.   /* Initialize GStreamer */  
  178.   gst_init (&argc, &argv);  
  179.     
  180.   g_print ("Discovering '%s'\n", uri);  
  181.     
  182.   /* Instantiate the Discoverer */  
  183.   data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);  
  184.   if (!data.discoverer) {  
  185.     g_print ("Error creating discoverer instance: %s\n", err->message);  
  186.     g_clear_error (&err);  
  187.     return -1;  
  188.   }  
  189.     
  190.   /* Connect to the interesting signals */  
  191.   g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);  
  192.   g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);  
  193.     
  194.   /* Start the discoverer process (nothing to do yet) */  
  195.   gst_discoverer_start (data.discoverer);  
  196.     
  197.   /* Add a request to process asynchronously the URI passed through the command line */  
  198.   if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {  
  199.     g_print ("Failed to start discovering URI '%s'\n", uri);  
  200.     g_object_unref (data.discoverer);  
  201.     return -1;  
  202.   }  
  203.     
  204.   /* Create a GLib Main Loop and set it to run, so we can wait for the signals */  
  205.   data.loop = g_main_loop_new (NULL, FALSE);  
  206.   g_main_loop_run (data.loop);  
  207.     
  208.   /* Stop the discoverer process */  
  209.   gst_discoverer_stop (data.discoverer);  
  210.     
  211.   /* Free resources */  
  212.   g_object_unref (data.discoverer);  
  213.   g_main_loop_unref (data.loop);  
  214.     
  215.   return 0;  
  216. }</span>  


工作流程

      下面是使用GstDiscoverer的主要步骤。


[objc]  view plain  copy
 
  1. <span style="font-size:14px;">  /* Instantiate the Discoverer */  
  2.   data.discoverer = gst_discoverer_new (55 * GST_SECOND, &err);  
  3.   if (!data.discoverer) {  
  4.     g_print ("Error creating discoverer instance: %s\n", err->message);  
  5.     g_clear_error (&err);  
  6.     return -1;  
  7.   }</span>  
      gst_discover_new()会创建一个Discoverer对象,第一个参数是超时时间(使用纳秒做单位)。



[objc]  view plain  copy
 
  1. <span style="font-size:14px;">  /* Connect to the interesting signals */  
  2.   g_signal_connect (data.discoverer"discovered", G_CALLBACK (on_discovered_cb), &data);  
  3.   g_signal_connect (data.discoverer"finished", G_CALLBACK (on_finished_cb), &data);</span>  


      像平常一样对必要的的信号注册回调,我们在他们的回调里面继续讨论。


[objc]  view plain  copy
 
  1. <span style="font-size:14px;">  /* Start the discoverer process (nothing to do yet) */  
  2.   gst_discoverer_start (data.discoverer);</span>  
      gst_discover_start()启动检查进程,但我们还没有提供URI,下面就是提供URI了:



[objc]  view plain  copy
 
  1. <span style="font-size:14px;">  /* Add a request to process asynchronously the URI passed through the command line */  
  2.   if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {  
  3.     g_print ("Failed to start discovering URI '%s'\n", uri);  
  4.     g_object_unref (data.discoverer);  
  5.     return -1;  
  6.   }</span>  
      gst_discoverer_discover_uri_async()会把需要检查的URI放入队列,这个函数支持把多个URI入队。在检查过程把所有的URI都检查过之后,会调用注册的回调函数。



[objc]  view plain  copy
 
  1. <span style="font-size:14px;">  /* Create a GLib Main Loop and set it to run, so we can wait for the signals */  
  2.   data.loop = g_main_loop_new (NULL, FALSE);  
  3.   g_main_loop_run (data.loop);</span>  


      GLib已经在运行主循环了,我们在on_finished_cb回调中调用g_main_loop_quit()来退出主循环。


[objc]  view plain  copy
 
  1. <span style="font-size:14px;">  /* Stop the discoverer process */  
  2.   gst_discoverer_stop (data.discoverer);</span>  
      一旦我们检查结束,我们会调用gst_discoverer_stop()来停止,并且调用g_object_unref()来释放资源。


      现在让我们来看一下我们注册的回调函数:


[objc]  view plain  copy
 
  1. <span style="font-size:14px;">/* This function is called every time the discoverer has information regarding 
  2.  * one of the URIs we provided.*/  
  3. static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {  
  4.   GstDiscovererResult result;  
  5.   const gchar *uri;  
  6.   const GstTagList *tags;  
  7.   GstDiscovererStreamInfo *sinfo;  
  8.     
  9.   uri = gst_discoverer_info_get_uri (info);  
  10.   result = gst_discoverer_info_get_result (info);</span>  


      当我们运行到这些代码时,discoverer已经完成了一个URI的检查了,用GstDiscovererInfo这个数据结构把内容传给我们。

      这里第一步是用gst_discoverer_info_get_uri()方法去获得调用这个回调的URI(因为可能存在多个URI),然后通过gst_discoverer_info_get_result()方法来获得数据。


[objc]  view plain  copy
 
  1. switch (result) {  
  2.   case GST_DISCOVERER_URI_INVALID:  
  3.     g_print ("Invalid URI '%s'\n", uri);  
  4.     break;  
  5.   case GST_DISCOVERER_ERROR:  
  6.     g_print ("Discoverer error: %s\n", err->message);  
  7.     break;  
  8.   case GST_DISCOVERER_TIMEOUT:  
  9.     g_print ("Timeout\n");  
  10.     break;  
  11.   case GST_DISCOVERER_BUSY:  
  12.     g_print ("Busy\n");  
  13.     break;  
  14.   case GST_DISCOVERER_MISSING_PLUGINS:{  
  15.     const GstStructure *s;  
  16.     gchar *str;  
  17.       
  18.     s = gst_discoverer_info_get_misc (info);  
  19.     str = gst_structure_to_string (s);  
  20.       
  21.     g_print ("Missing plugins: %s\n", str);  
  22.     g_free (str);  
  23.     break;  
  24.   }  
  25.   case GST_DISCOVERER_OK:  
  26.     g_print ("Discovered '%s'\n", uri);  
  27.     break;  
  28. }  
  29.   
  30. if (result != GST_DISCOVERER_OK) {  
  31.   g_printerr ("This URI cannot be played\n");  
  32.   return;  
  33. }  
      正如代码显示的那样,除了GST_DISCOVERER_OK之外,任何一个结果都意味着有某种问题,这个URI不能播放。不能播放的原因是多种多样的,但枚举出来后也比较清晰(GST_DISCOVERER_BUSY只可能在同步模式下出现,这里是不可能出现的)。


      如果没有发生错误,那么可以用gst_discoverer_info_get_*系列方法从GstDiscovererInfo这个结构里面获得数据了。比如,用gst_discoverer_info_get_duration()方法可以获得播放总长度。

      信息的位是由列表组成的,就像标签和流信息,需要再解析一下:


[objc]  view plain  copy
 
  1. tags = gst_discoverer_info_get_tags (info);  
  2. if (tags) {  
  3.   g_print ("Tags:\n");  
  4.   gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));  
  5. }  
      标签是附着在媒体上得元数据。它们可以用gst_tag_list_foreach()方法来检查,每检查到一个标签就调用一次print_tag_foreach()。至于print_tag_foreach()方法的代码比较浅显,可以直接读懂。



[objc]  view plain  copy
 
  1. sinfo = gst_discoverer_info_get_stream_info (info);  
  2. if (!sinfo)  
  3.   return;  
  4.   
  5. g_print ("Stream information:\n");  
  6.   
  7. print_topology (sinfo, 1);  
  8.   
  9. gst_discoverer_stream_info_unref (sinfo);  
      gst_discoverer_info_get_stream_info()会返回一个GstDiscovererStreamInfo类型的数据,print_topology()函数会解析这个数据结构,然后调用gst_discoverer_stream_info_unref()来释放资源。



[objc]  view plain  copy
 
  1. /* Print information regarding a stream and its substreams, if any */  
  2. static void print_topology (GstDiscovererStreamInfo *info, gint depth) {  
  3.   GstDiscovererStreamInfo *next;  
  4.     
  5.   if (!info)  
  6.     return;  
  7.     
  8.   print_stream_info (info, depth);  
  9.     
  10.   next = gst_discoverer_stream_info_get_next (info);  
  11.   if (next) {  
  12.     print_topology (next, depth + 1);  
  13.     gst_discoverer_stream_info_unref (next);  
  14.   } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {  
  15.     GList *tmp, *streams;  
  16.       
  17.     streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));  
  18.     for (tmp = streams; tmp; tmp = tmp->next) {  
  19.       GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;  
  20.       print_topology (tmpinf, depth + 1);  
  21.     }  
  22.     gst_discoverer_stream_info_list_free (streams);  
  23.   }  
  24. }  
      print_stream_info()函数的代码也是很简单直接的,它也调用了print_tag_foreach方法来打印流的capabilities和相关联的tags。


      print_topology方法会寻找下一个流来显示。如果get_discoverer_stream_info_get_next()返回一个非空流信息的话,它需要被显示。另一方面,如果我们是一个容器,我们每一个用get_discoverer_container_info_get_streams()方法获得的子流都需要递归的调用print_topology方法。当然,如果我们是一个叶子流了,就不在需要继续递归下去了。

你可能感兴趣的:(GStreamer基础教程09——收集媒体信息)