GStreamer播放教程02——字幕管理

目標

      這篇教程和上一篇非常相似,但不是切換音頻流,而是字幕了。這次我們會展示:

      如何選擇選擇字幕流

      如何引入外部的字幕

      如何客製化字幕使用的字體


介紹

      我們都知道一個文件可以有多個音視頻流並且可以使用playerbin2的current-audio和current-video屬性很方便的進行切換。切換字幕也是一樣的方便。

      就和音視頻一樣,playbin2會選擇解碼好的字幕,而且GStreamer的插件設計也很容易支持一種新的文件結構。

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

      本教程會打開一個包含5個字幕流的文件,並從外界在導入一個字幕(希臘語)。


多語言字幕的播放器

#include <gst/gst.h>
  
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
  GstElement *playbin2;  /* Our one and only element */
  
  gint n_video;          /* Number of embedded video streams */
  gint n_audio;          /* Number of embedded audio streams */
  gint n_text;           /* Number of embedded subtitle streams */
  
  gint current_video;    /* Currently playing video stream */
  gint current_audio;    /* Currently playing audio stream */
  gint current_text;     /* Currently playing subtitle stream */
  
  GMainLoop *main_loop;  /* GLib's Main Loop */
} CustomData;
  
/* playbin2 flags */
typedef enum {
  GST_PLAY_FLAG_VIDEO         = (1 << 0), /* We want video output */
  GST_PLAY_FLAG_AUDIO         = (1 << 1), /* We want audio output */
  GST_PLAY_FLAG_TEXT          = (1 << 2)  /* We want subtitle output */
} GstPlayFlags;
  
/* Forward definition for the message and keyboard processing functions */
static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data);
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data);
  
int main(int argc, char *argv[]) {
  CustomData data;
  GstBus *bus;
  GstStateChangeReturn ret;
  gint flags;
  GIOChannel *io_stdin;
  
  /* Initialize GStreamer */
  gst_init (&argc, &argv);
   
  /* Create the elements */
  data.playbin2 = gst_element_factory_make ("playbin2", "playbin2");
  
  if (!data.playbin2) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }
  
  /* Set the URI to play */
  g_object_set (data.playbin2, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.ogv", NULL);
  
  /* Set the subtitle URI to play and some font description */
  g_object_set (data.playbin2, "suburi", "http://docs.gstreamer.com/media/sintel_trailer_gr.srt", NULL);
  g_object_set (data.playbin2, "subtitle-font-desc", "Sans, 18", NULL);
  
  /* Set flags to show Audio, Video and Subtitles */
  g_object_get (data.playbin2, "flags", &flags, NULL);
  flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_TEXT;
  g_object_set (data.playbin2, "flags", flags, NULL);
  
  /* Add a bus watch, so we get notified when a message arrives */
  bus = gst_element_get_bus (data.playbin2);
  gst_bus_add_watch (bus, (GstBusFunc)handle_message, &data);
  
  /* Add a keyboard watch so we get notified of keystrokes */
#ifdef _WIN32
  io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
#else
  io_stdin = g_io_channel_unix_new (fileno (stdin));
#endif
  g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);
  
  /* Start playing */
  ret = gst_element_set_state (data.playbin2, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (data.playbin2);
    return -1;
  }
  
  /* Create a GLib Main Loop and set it to run */
  data.main_loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (data.main_loop);
  
  /* Free resources */
  g_main_loop_unref (data.main_loop);
  g_io_channel_unref (io_stdin);
  gst_object_unref (bus);
  gst_element_set_state (data.playbin2, GST_STATE_NULL);
  gst_object_unref (data.playbin2);
  return 0;
}
  
/* Extract some metadata from the streams and print it on the screen */
static void analyze_streams (CustomData *data) {
  gint i;
  GstTagList *tags;
  gchar *str;
  guint rate;
  
  /* Read some properties */
  g_object_get (data->playbin2, "n-video", &data->n_video, NULL);
  g_object_get (data->playbin2, "n-audio", &data->n_audio, NULL);
  g_object_get (data->playbin2, "n-text", &data->n_text, NULL);
  
  g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n",
    data->n_video, data->n_audio, data->n_text);
  
  g_print ("\n");
  for (i = 0; i < data->n_video; i++) {
    tags = NULL;
    /* Retrieve the stream's video tags */
    g_signal_emit_by_name (data->playbin2, "get-video-tags", i, &tags);
    if (tags) {
      g_print ("video stream %d:\n", i);
      gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
      g_print ("  codec: %s\n", str ? str : "unknown");
      g_free (str);
      gst_tag_list_free (tags);
    }
  }
  
  g_print ("\n");
  for (i = 0; i < data->n_audio; i++) {
    tags = NULL;
    /* Retrieve the stream's audio tags */
    g_signal_emit_by_name (data->playbin2, "get-audio-tags", i, &tags);
    if (tags) {
      g_print ("audio stream %d:\n", i);
      if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {
        g_print ("  codec: %s\n", str);
        g_free (str);
      }
      if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
        g_print ("  language: %s\n", str);
        g_free (str);
      }
      if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {
        g_print ("  bitrate: %d\n", rate);
      }
      gst_tag_list_free (tags);
    }
  }
  
  g_print ("\n");
  for (i = 0; i < data->n_text; i++) {
    tags = NULL;
    /* Retrieve the stream's subtitle tags */
    g_print ("subtitle stream %d:\n", i);
    g_signal_emit_by_name (data->playbin2, "get-text-tags", i, &tags);
    if (tags) {
      if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
        g_print ("  language: %s\n", str);
        g_free (str);
      }
      gst_tag_list_free (tags);
    } else {
      g_print ("  no tags found\n");
    }
  }
  
  g_object_get (data->playbin2, "current-video", &data->current_video, NULL);
  g_object_get (data->playbin2, "current-audio", &data->current_audio, NULL);
  g_object_get (data->playbin2, "current-text", &data->current_text, NULL);
  
  g_print ("\n");
  g_print ("Currently playing video stream %d, audio stream %d and subtitle stream %d\n",
      data->current_video, data->current_audio, data->current_text);
  g_print ("Type any number and hit ENTER to select a different subtitle stream\n");
}
  
/* Process messages from GStreamer */
static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data) {
  GError *err;
  gchar *debug_info;
  
  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_ERROR:
      gst_message_parse_error (msg, &err, &debug_info);
      g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
      g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
      g_clear_error (&err);
      g_free (debug_info);
      g_main_loop_quit (data->main_loop);
      break;
    case GST_MESSAGE_EOS:
      g_print ("End-Of-Stream reached.\n");
      g_main_loop_quit (data->main_loop);
      break;
    case GST_MESSAGE_STATE_CHANGED: {
      GstState old_state, new_state, pending_state;
      gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
      if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin2)) {
        if (new_state == GST_STATE_PLAYING) {
          /* Once we are in the playing state, analyze the streams */
          analyze_streams (data);
        }
      }
    } break;
    default:
      break;
  }
  
  /* We want to keep receiving messages */
  return TRUE;
}
  
/* Process keyboard input */
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
  gchar *str = NULL;
  
  if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) {
    int index = atoi (str);
    if (index < 0 || index >= data->n_text) {
      g_printerr ("Index out of bounds\n");
    } else {
      /* If the input was a valid subtitle stream index, set the current subtitle stream */
      g_print ("Setting current subtitle stream to %d\n", index);
      g_object_set (data->playbin2, "current-text", index, NULL);
    }
  }
  g_free (str);
  return TRUE;
}

工作流程

      這篇教程和上篇教程的例子只有很小的差別,讓我們就看看這些不同的地方吧。

  /* Set the subtitle URI to play and some font description */
  g_object_set (data.playbin2, "suburi", "http://docs.gstreamer.com/media/sintel_trailer_gr.srt", NULL);
  g_object_set (data.playbin2, "subtitle-font-desc", "Sans, 18", NULL);

      在設置媒體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

  /* Set flags to show Audio, Video and Subtitles */
  g_object_get (data.playbin2, "flags", &flags, NULL);
  flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_TEXT;
  g_object_set (data.playbin2, "flags", flags, NULL);

      我們設置flags屬性,把音頻,視頻和字幕的開關都打開。

      剩下的部分和上一篇裏面的例子是一樣的,除了鍵盤輸入是改變current-text的屬性而不是current-audio的屬性。這裏再強調一下,切換流不會馬上起作用,因爲緩衝了許多解碼好的數據了。

      








發佈了79 篇原創文章 · 獲贊 42 · 訪問量 111萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章