【GStreamer开发】GStreamer播放教程02——字幕管理

目标

      这篇教程和上一篇非常相似,但不是切换音频流,而是字幕了。这次我们会展示:

      如何选择选择字幕流

      如何引入外部的字幕

      如何客制化字幕使用的字体


介绍

      我们都知道一个文件可以有多个音视频流并且可以使用playerbin2的current-audio和current-video属性很方便的进行切换。切换字幕也是一样的方便。

      就和音视频一样,playbin2会选择解码好的字幕,而且GStreamer的插件设计也很容易支持一种新的文件结构。

      但字幕还是有自己的特殊之处,除了可以嵌入文件里面,playbin2还支持使用外界的URI来提供字幕。

      本教程会打开一个包含5个字幕流的文件,并从外界在导入一个字幕(希腊语)。


多语言字幕的播放器

[objc] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;">#include <gst/gst.h>  
  2.     
  3. /* Structure to contain all our information, so we can pass it around */  
  4. typedef struct _CustomData {  
  5.   GstElement *playbin2;  /* Our one and only element */  
  6.     
  7.   gint n_video;          /* Number of embedded video streams */  
  8.   gint n_audio;          /* Number of embedded audio streams */  
  9.   gint n_text;           /* Number of embedded subtitle streams */  
  10.     
  11.   gint current_video;    /* Currently playing video stream */  
  12.   gint current_audio;    /* Currently playing audio stream */  
  13.   gint current_text;     /* Currently playing subtitle stream */  
  14.     
  15.   GMainLoop *main_loop;  /* GLib's Main Loop */  
  16. } CustomData;  
  17.     
  18. /* playbin2 flags */  
  19. typedef enum {  
  20.   GST_PLAY_FLAG_VIDEO         = (1 << 0), /* We want video output */  
  21.   GST_PLAY_FLAG_AUDIO         = (1 << 1), /* We want audio output */  
  22.   GST_PLAY_FLAG_TEXT          = (1 << 2)  /* We want subtitle output */  
  23. } GstPlayFlags;  
  24.     
  25. /* Forward definition for the message and keyboard processing functions */  
  26. static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data);  
  27. static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data);  
  28.     
  29. int main(int argc, charchar *argv[]) {  
  30.   CustomData data;  
  31.   GstBus *bus;  
  32.   GstStateChangeReturn ret;  
  33.   gint flags;  
  34.   GIOChannel *io_stdin;  
  35.     
  36.   /* Initialize GStreamer */  
  37.   gst_init (&argc, &argv);  
  38.      
  39.   /* Create the elements */  
  40.   data.playbin2 = gst_element_factory_make ("playbin2""playbin2");  
  41.     
  42.   if (!data.playbin2) {  
  43.     g_printerr ("Not all elements could be created.\n");  
  44.     return -1;  
  45.   }  
  46.     
  47.   /* Set the URI to play */  
  48.   g_object_set (data.playbin2"uri""http://docs.gstreamer.com/media/sintel_trailer-480p.ogv"NULL);  
  49.     
  50.   /* Set the subtitle URI to play and some font description */  
  51.   g_object_set (data.playbin2"suburi""http://docs.gstreamer.com/media/sintel_trailer_gr.srt"NULL);  
  52.   g_object_set (data.playbin2"subtitle-font-desc""Sans, 18"NULL);  
  53.     
  54.   /* Set flags to show Audio, Video and Subtitles */  
  55.   g_object_get (data.playbin2"flags", &flags, NULL);  
  56.   flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_TEXT;  
  57.   g_object_set (data.playbin2"flags", flags, NULL);  
  58.     
  59.   /* Add a bus watch, so we get notified when a message arrives */  
  60.   bus = gst_element_get_bus (data.playbin2);  
  61.   gst_bus_add_watch (bus, (GstBusFunc)handle_message, &data);  
  62.     
  63.   /* Add a keyboard watch so we get notified of keystrokes */  
  64. #ifdef _WIN32  
  65.   io_stdin = g_io_channel_win32_new_fd (fileno (stdin));  
  66. #else  
  67.   io_stdin = g_io_channel_unix_new (fileno (stdin));  
  68. #endif  
  69.   g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);  
  70.     
  71.   /* Start playing */  
  72.   ret = gst_element_set_state (data.playbin2, GST_STATE_PLAYING);  
  73.   if (ret == GST_STATE_CHANGE_FAILURE) {  
  74.     g_printerr ("Unable to set the pipeline to the playing state.\n");  
  75.     gst_object_unref (data.playbin2);  
  76.     return -1;  
  77.   }  
  78.     
  79.   /* Create a GLib Main Loop and set it to run */  
  80.   data.main_loop = g_main_loop_new (NULL, FALSE);  
  81.   g_main_loop_run (data.main_loop);  
  82.     
  83.   /* Free resources */  
  84.   g_main_loop_unref (data.main_loop);  
  85.   g_io_channel_unref (io_stdin);  
  86.   gst_object_unref (bus);  
  87.   gst_element_set_state (data.playbin2, GST_STATE_NULL);  
  88.   gst_object_unref (data.playbin2);  
  89.   return 0;  
  90. }  
  91.     
  92. /* Extract some metadata from the streams and print it on the screen */  
  93. static void analyze_streams (CustomData *data) {  
  94.   gint i;  
  95.   GstTagList *tags;  
  96.   gchar *str;  
  97.   guint rate;  
  98.     
  99.   /* Read some properties */  
  100.   g_object_get (data->playbin2"n-video", &data->n_video, NULL);  
  101.   g_object_get (data->playbin2"n-audio", &data->n_audio, NULL);  
  102.   g_object_get (data->playbin2"n-text", &data->n_text, NULL);  
  103.     
  104.   g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n",  
  105.     data->n_video, data->n_audio, data->n_text);  
  106.     
  107.   g_print ("\n");  
  108.   for (i = 0; i < data->n_video; i++) {  
  109.     tags = NULL;  
  110.     /* Retrieve the stream's video tags */  
  111.     g_signal_emit_by_name (data->playbin2"get-video-tags", i, &tags);  
  112.     if (tags) {  
  113.       g_print ("video stream %d:\n", i);  
  114.       gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);  
  115.       g_print ("  codec: %s\n", str ? str : "unknown");  
  116.       g_free (str);  
  117.       gst_tag_list_free (tags);  
  118.     }  
  119.   }  
  120.     
  121.   g_print ("\n");  
  122.   for (i = 0; i < data->n_audio; i++) {  
  123.     tags = NULL;  
  124.     /* Retrieve the stream's audio tags */  
  125.     g_signal_emit_by_name (data->playbin2"get-audio-tags", i, &tags);  
  126.     if (tags) {  
  127.       g_print ("audio stream %d:\n", i);  
  128.       if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {  
  129.         g_print ("  codec: %s\n", str);  
  130.         g_free (str);  
  131.       }  
  132.       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {  
  133.         g_print ("  language: %s\n", str);  
  134.         g_free (str);  
  135.       }  
  136.       if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {  
  137.         g_print ("  bitrate: %d\n", rate);  
  138.       }  
  139.       gst_tag_list_free (tags);  
  140.     }  
  141.   }  
  142.     
  143.   g_print ("\n");  
  144.   for (i = 0; i < data->n_text; i++) {  
  145.     tags = NULL;  
  146.     /* Retrieve the stream's subtitle tags */  
  147.     g_print ("subtitle stream %d:\n", i);  
  148.     g_signal_emit_by_name (data->playbin2"get-text-tags", i, &tags);  
  149.     if (tags) {  
  150.       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {  
  151.         g_print ("  language: %s\n", str);  
  152.         g_free (str);  
  153.       }  
  154.       gst_tag_list_free (tags);  
  155.     } else {  
  156.       g_print ("  no tags found\n");  
  157.     }  
  158.   }  
  159.     
  160.   g_object_get (data->playbin2"current-video", &data->current_video, NULL);  
  161.   g_object_get (data->playbin2"current-audio", &data->current_audio, NULL);  
  162.   g_object_get (data->playbin2"current-text", &data->current_text, NULL);  
  163.     
  164.   g_print ("\n");  
  165.   g_print ("Currently playing video stream %d, audio stream %d and subtitle stream %d\n",  
  166.       data->current_video, data->current_audio, data->current_text);  
  167.   g_print ("Type any number and hit ENTER to select a different subtitle stream\n");  
  168. }  
  169.     
  170. /* Process messages from GStreamer */  
  171. static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data) {  
  172.   GError *err;  
  173.   gchar *debug_info;  
  174.     
  175.   switch (GST_MESSAGE_TYPE (msg)) {  
  176.     case GST_MESSAGE_ERROR:  
  177.       gst_message_parse_error (msg, &err, &debug_info);  
  178.       g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);  
  179.       g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");  
  180.       g_clear_error (&err);  
  181.       g_free (debug_info);  
  182.       g_main_loop_quit (data->main_loop);  
  183.       break;  
  184.     case GST_MESSAGE_EOS:  
  185.       g_print ("End-Of-Stream reached.\n");  
  186.       g_main_loop_quit (data->main_loop);  
  187.       break;  
  188.     case GST_MESSAGE_STATE_CHANGED: {  
  189.       GstState old_state, new_state, pending_state;  
  190.       gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);  
  191.       if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin2)) {  
  192.         if (new_state == GST_STATE_PLAYING) {  
  193.           /* Once we are in the playing state, analyze the streams */  
  194.           analyze_streams (data);  
  195.         }  
  196.       }  
  197.     } break;  
  198.     default:  
  199.       break;  
  200.   }  
  201.     
  202.   /* We want to keep receiving messages */  
  203.   return TRUE;  
  204. }  
  205.     
  206. /* Process keyboard input */  
  207. static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {  
  208.   gchar *str = NULL;  
  209.     
  210.   if (g_io_channel_read_line (source, &str, NULLNULLNULL) == G_IO_STATUS_NORMAL) {  
  211.     int index = atoi (str);  
  212.     if (index < 0 || index >= data->n_text) {  
  213.       g_printerr ("Index out of bounds\n");  
  214.     } else {  
  215.       /* If the input was a valid subtitle stream index, set the current subtitle stream */  
  216.       g_print ("Setting current subtitle stream to %d\n", index);  
  217.       g_object_set (data->playbin2"current-text", index, NULL);  
  218.     }  
  219.   }  
  220.   g_free (str);  
  221.   return TRUE;  
  222. }  
  223. </span>  

工作流程

      这篇教程和上篇教程的例子只有很小的差别,让我们就看看这些不同的地方吧。

[objc] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;">  /* Set the subtitle URI to play and some font description */  
  2.   g_object_set (data.playbin2"suburi""http://docs.gstreamer.com/media/sintel_trailer_gr.srt"NULL);  
  3.   g_object_set (data.playbin2"subtitle-font-desc""Sans, 18"NULL);</span>  

      在设置媒体URI之后,我们设置了suburi属性,这就让playbin2获得了字幕流的地址。在这个例子里面,文件里本身包含了多个字幕流了,这用suburi来设置的字幕会加入这个列表一起列出,并且是默认选择的。

      注意,在文件里面包含的字幕流是有元数据的(比如字幕的语言),然而外部的字幕流没有元数据。当运行这个例子时你会看到第一个字幕流没有语言的标签。

      subtitle-font-desc属性允许设置字幕的文本字体。因为使用了Pango库来进行字体的渲染,所以具体可以查询相关文档。

      简单概括一下,字符串字体的描述是根据[FAMILY-LIST][STYLE-OPTIONS][SIZE]来的,其中FAMILY-LIST是用逗号隔开的一系列可选字体,STYLE-OPTIONS是用空格来分开的一系列字体样式,SIZE则是字体大小。比如:

      sans bold 12

      serif, monospace bold italic condensed 16

      normal 10

      常见的字体包括:Normal,Sans,Serif和Monospace。

      常用的样式包括:Normal,Oblique,Italic

      常见的粗细包括:Ultra-Light,Light,Normal,Bold,Ultra-Bold,Heavy

      常见的变化包括:Normal,Small_Caps

      常见的拉伸包括:Ultra-Condensed,Extra-Condensed,Condensed,Semi-Condensed,Normal,Semi-Expanded,Extra-Expanded,Ultra-Expanded

[objc] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;">  /* Set flags to show Audio, Video and Subtitles */  
  2.   g_object_get (data.playbin2"flags", &flags, NULL);  
  3.   flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_TEXT;  
  4.   g_object_set (data.playbin2"flags", flags, NULL);</span>  

      我们设置flags属性,把音频,视频和字幕的开关都打开。

      剩下的部分和上一篇里面的例子是一样的,除了键盘输入是改变current-text的属性而不是current-audio的属性。这里再强调一下,切换流不会马上起作用,因为缓冲了许多解码好的数据了。

posted @ 2017-02-17 22:16  ZhangPYi  阅读(228)  评论(0编辑  收藏  举报