目标

我们前面已经使用过了playbin2这个element,它可以让我们做的很少而实现很多。本教程会展示当这个element的默认设置在一些特殊情形下不符合我们的需求是可以做的一些深度定制,我们会看到:

如何判断一个文件中有多少个流并区分开

如何获得每一个流的信息

作为一个编注,虽然这个element名字是playbin2,但因为原来的playbin已经废弃不用了,所以还是可以称作playbin而不会引起混淆。

介绍

很多时候,一个文件里面会含有一个视频流,多个音频流和字幕流。最常见的时含有一个视频流,一个音频流(5.1声道也是一个音频流的),但现在越来越多的文件开始包含多个配音,也就是有多个音频流。在这种情况下,用户如果选择了某个音频流,那么就会播放这个音频流的内容,忽略其它的音频流。

为了选择正确地流,用户需要知道一定的信息。比如,它们的语言。这个信息室作为一种“元数据”被嵌在流里面的,本教程会演示一下如何获得这个信息。

字幕和音视频一样可以嵌在文件中,我们在下一讲再详细讲这个部分。最后提一下,一个文件里面有多个视频流也是可以的,最典型的例子是DVD,包含多个角度的画面,不过这个情况很少很少的。

下面的代码会显示文件里面流的数目,他们相关的元数据并允许在播放时切换音频流。

多语言播放器

[objc] view
plain
 copy

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

工作流程

[objc] view
plain
 copy

  1. <span style="font-size:14px;">/* Structure to contain all our information, so we can pass it around */
  2. typedef struct _CustomData {
  3. ;  /* Our one and only element */
  4. gint n_video;          /* Number of embedded video streams */
  5. gint n_audio;          /* Number of embedded audio streams */
  6. gint n_text;           /* Number of embedded subtitle streams */
  7. gint current_video;    /* Currently playing video stream */
  8. gint current_audio;    /* Currently playing audio stream */
  9. gint current_text;     /* Currently playing subtitle stream */
  10. GMainLoop *main_loop;  /* GLib's Main Loop */
  11. } CustomData;</span>

像以前一样,我们把所有的数据都放在一个结构体中方便访问。在本教程中,我们需要知道每种流的数目以及当前播放的时哪个流。而且我们会使用一种不同的机制来等待可以进行交换的消息,所以我们需要一个GLib的主循环。

[objc] view
plain
 copy

  1. <span style="font-size:14px;">/* playbin2 flags */
  2. typedef enum {
  3. << 0), /* We want video output */
  4. << 1), /* We want audio output */
  5. << 2)  /* We want subtitle output */
  6. } GstPlayFlags;</span>

在后面我们会设置一些playbin2的标志,我们当然喜欢一个有意义的枚举类型,这样可以方便理解和操控。但playbin2只是一个plugin而不是GStreamer核心的一部分,这个枚举是看不见的,所以只能在代码里面再定义一次,这些标志的详细解释请参考playbib2文档的GstPlayFlags部分。GObject是内省得,所以这些标志在运行时是可以获得的,只是比较笨重。

[objc] view
plain
 copy

  1. <span style="font-size:14px;">/* Forward definition for the message and keyboard processing functions */
  2. static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data);
  3. static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data);</span>

上面声明了我们会用到的两个回调函数。handle_message用来处理GStreamer的消息,我们已经比较熟悉了;handle_keyboard是用来处理按键,因为这篇教程里面需要一些交互。

因为playbin2一个element组成的pipeline,所以我们跳过了创建pipeline,初始化playbin2并赋予URI这部分。

下面我们来讲一些playbin2的属性:

[objc] view
plain
 copy

  1. /* Set flags to show Audio and Video but ignore Subtitles */
  2. g_object_get (data.playbin2, "flags", &flags, NULL);
  3. flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO;
  4. flags &= ~GST_PLAY_FLAG_TEXT;
  5. g_object_set (data.playbin2, "flags", flags, NULL);

通过设置flags属性,playbin2的行为是可以设置的。flags的属性可以有很多GstPlayFlags来组合而成,最主要的值是:

GST_PLAY_FLAG_VIDEO 允许视频渲染,如果这个标志没有设置,则没有视频输出
GST_PLAY_FLAG_AUDIO 允许音频渲染,如果这个标志没有设置,则没有音频输出
GST_PLAY_FLAG_TEXT 允许字幕渲染,如果这个标志没有设置,则没有字幕显示
GST_PLAY_FLAG_VIS 允许在没有视频流时进行可视化渲染,后面教程会讲到
GST_PLAY_FLAG_DOWNLOAD 参见《GStreamer基础教程12——流》以及后续教程
GST_PLAY_FLAG_BUFFERING 参见《GStreamer基础教程12——流》以及后续教程
GST_PLAY_FLAG_DEINTERLACE 如果视频是隔行扫描的,那么在显示时改成逐行扫描

在我们这个例子中,因为仅仅是demo,我们仅仅打开是音频和视频,关闭了字幕。其他标志仍然保持原样。

[objc] view
plain
 copy

  1. /* Set connection speed. This will affect some internal decisions of playbin2 */
  2. 6, NULL);

该属性在这个例子中并非真的有用。connection-speed是设置playbin2的最大网络连接速度,为了防止服务器上有多个版本的媒体文件,playbin2会选择最合适的。这个主要是结合流协议(mms或者rstp)来用的。

我们逐个的设置这些属性,但我们也可以仅调用g_object_set()一次。

[objc] view
plain
 copy

  1. 6, NULL);

这就是为什么g_object_set()这个方法需要NULL来做最后一个参数。

[objc] view
plain
 copy

  1. /* Add a keyboard watch so we get notified of keystrokes */
  2. 2
  3. 2_new_fd (fileno (stdin));
  4. lse
  5. io_stdin = g_io_channel_unix_new (fileno (stdin));
  6. ndif
  7. g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);

这几行连接了一个标准输入(键盘)和一个回调函数。这里使用的机制是GLib的,并非是基于GStreamer的,所以就不展开了。应用一般都有自己的处理输入的方式,GStreamer和这个方面没有什么关系。

[objc] view
plain
 copy

  1. /* Create a GLib Main Loop and set it to run */
  2. data.main_loop = g_main_loop_new (NULL, FALSE);
  3. g_main_loop_run (data.main_loop);

为了交互,我们不再手动轮询GStreamer总线了。我们创建了GMainLoop并且通过g_main_loop_run()来让它运行起来。这个函数会阻塞住线程,直到g_main_loop_quit()被调用才返回。在这段时间里,它会调用我们注册的回调函数:在总线上有消息时调用handle_message,在有按键时调用handle_keyboard。

在handle_message里面除了在pipeline转移到PLAYING状态时调用analyze_stream函数外没什么新内容。

[objc] view
plain
 copy

  1. /* Extract some metadata from the streams and print it on the screen */
  2. static void analyze_streams (CustomData *data) {
  3. gint i;
  4. GstTagList *tags;
  5. gchar *str;
  6. guint rate;
  7. /* Read some properties */
  8. , "n-video", &data->n_video, NULL);
  9. , "n-audio", &data->n_audio, NULL);
  10. , "n-text", &data->n_text, NULL);

正如注释所说的,这个函数是用来手机媒体的信息并打印出来。视频流的数目、音频流的数目合字幕的数目都可以直接从n-video,n-audio,n-text这几个属性读出来。

[objc] view
plain
 copy

  1. ; i < data->n_video; i++) {
  2. tags = NULL;
  3. /* Retrieve the stream's video tags */
  4. , "get-video-tags", i, &tags);
  5. if (tags) {
  6. g_print ("video stream %d:\n", i);
  7. gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
  8. g_print ("  codec: %s\n", str ? str : "unknown");
  9. g_free (str);
  10. gst_tag_list_free (tags);
  11. }
  12. }

现在,对于每一个流来说,我们需要获得它的元数据。元数据是存在一个GstTagList的结构体里面,这个GstTagList通过g_signal_emit_by_name()可以把流里面对应的tag都取出来。然后可以用gst_tag_list_get_*这一类函数来访问这些tag,这个例子中用的就是gst_tag_list_get_string()方法。

playbin2定义了2个action信号来获得元数据:get-video-tags,get-audio-tags和get-text-tags。这些tags的名字是标准的并可以在GstTagList文档里面找到。在这个例子中我们关注的是GST_TAG_LANGUAGE_CODE这个tag和GST_TAG_*_CODEC(audio,video和text)。

[objc] view
plain
 copy

  1. , "current-video", &data->current_video, NULL);
  2. , "current-audio", &data->current_audio, NULL);
  3. , "current-text", &data->current_text, NULL);

一旦我们获得了所有需要的元数据,我们就可以通过playbin2的3个属性:current-video、current-audio、current-text来获得当前的选择。

[objc] view
plain
 copy

  1. /* Process keyboard input */
  2. static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
  3. gchar *str = NULL;
  4. if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) {
  5. int index = atoi (str);
  6. || index >= data->n_audio) {
  7. g_printerr ("Index out of bounds\n");
  8. } else {
  9. /* If the input was a valid audio stream index, set the current audio stream */
  10. g_print ("Setting current audio stream to %d\n", index);
  11. , "current-audio", index, NULL);
  12. }
  13. }
  14. g_free (str);
  15. return TRUE;
  16. }

最后,我们允许用户在播放中更换音频流。这个基本的功能实现是通过在标准输入(键盘)读取到一个值,然后设置current-audio这个属性。

请记住切换不是立刻生效的。前面已经解析出来的音频内容会继续播放一阵,直到新的流解析出来的数据被用来发声。这个延时是根据容器里面流的解复用情况以及前面缓冲了多少数据来确定的。

如果你运行这个教程,那么你可以通过按0,1或者2来选择语言。


【GStreamer开发】GStreamer播放教程01——playbin2的使用的更多相关文章

  1. 【GStreamer开发】GStreamer播放教程08——视频解码的硬件加速

    目标 视频的硬件解码近来发展非常快速,尤其是在低功耗的设备上.本教程会讲述一些硬件加速的背景知识并解释一下GStreamer是怎么做的. 悄悄告诉你,如果设置正确地话,我们什么也不用做,GStream ...

  2. 【GStreamer开发】GStreamer播放教程03——pipeline的快捷访问

    目的 <GStreamer08--pipeline的快捷访问>展示了一个应用如何用appsrc和appsink这两个特殊的element在pipeline中手动输入/提取数据.playbi ...

  3. 【GStreamer开发】GStreamer基础教程10——GStreamer工具

    目标 GStreamer提供了一系列方便使用的工具.这篇教程里不牵涉任何代码,但还是会讲一些有用的内容: 如何在命令行下建立一个pipeline--完全不使用C 如何找出一个element的Capab ...

  4. 【GStreamer开发】GStreamer基础教程05——集成GUI工具

    目标 本教程展示了如何在GStreamer集成一个GUI(比如:GTK+).最基本的原则是GStreamer处理多媒体的播放而GUI处理和用户的交互. 在这个教程里面,我们可以学到: 如何告诉GStr ...

  5. 【GStreamer开发】GStreamer基础教程04——时间管理

    目标 本教程主要讲述一些和时间相关的内容.主要包括: 1. 如何问pipeline查询到流的总时间和当前播放的时间 2. 如何在流内部实现跳转功能 介绍 GstQuery是向一个element或者pa ...

  6. gstreamer应用开发(播放器)之旅

    GStreamer开发,主要分为两块:应用开发.插件开发. 插件开发人员,通常是编解码库的作者(做出了编解码库后,希望gstreamer能用起来这个库,因此增加这个适配层).芯片原厂人员(将自家的hw ...

  7. 安装gstreamer开发环境

    ubuntu中安装gstreamer开发环境: * 安装gstreamer基本库,工具,以及插件 sudo apt--dev gstreamer-tools gstreamer0.-tools gst ...

  8. Android快乐贪吃蛇游戏实战项目开发教程-01项目概述与目录

    一.项目简介 贪吃蛇是一个很经典的游戏,也很适合用来学习.本教程将和大家一起做一个Android版的贪吃蛇游戏. 我已经将做好的案例上传到了应用宝,无病毒.无广告,大家可以放心下载下来把玩一下.应用宝 ...

  9. 【转】一步一步教你在Ubuntu12.04搭建gstreamer开发环境

    原文网址:http://blog.csdn.net/xsl1990/article/details/8333062 闲得蛋疼    无聊寂寞冷    随便写写弄弄 看到网上蛮多搭建gstreamer开 ...

随机推荐

  1. RookeyFrame 隐藏 首次加载菜单 的伸缩动画

    一进入系统,然后点击菜单“系统管理”,会看到展开的“系统设置”菜单,又缩回去了,每次都会有(处女座看到就想改). 隐藏这个动画的JS:jquery.easyui.min.js,这个JS里面有个方法“_ ...

  2. Code Chef JUNE Challenge 2019题解

    题面 \(SUMAGCD\) 先去重,易知答案一定是一个数单独一组剩下的一组,前缀后缀\(gcd\)一下就行了 //quming #include<bits/stdc++.h> #defi ...

  3. 简要描述Python的垃圾回收机制(garbage collection)

    这里能说的很多.你应该提到下面几个主要的点: Python在内存中存储了每个对象的引用计数(reference count).如果计数值变成0,那么相应的对象就会小时,分配给该对象的内存就会释放出来用 ...

  4. Java 获取客户端真实IP地址

    本文基于方法 HttpServletRequest.getHeader 和 HttpServletRequest.getRemoteAddr 介绍如何在服务器端获取客户端真实IP地址. 业务背景 服务 ...

  5. CTYZ的树论赛(P5557 旅行/P5558 心上秋/P5559 失昼城的守星使)

    总结 由于受中秋节影响,没能在比赛时间内切掉\(T3\) 思维难度\(T1<T2<T3\),代码难度\(T1>T2>T3\) P5557 旅行 显然跳到环上去后就可以直接模了, ...

  6. 编译安装-httpd-2.2.15.tar.gz

    编译安装(又称源代码安装) 找到httpd-2.2.15.tar.gz安装包并拖到桌面root文件夹里 解包阶段 tar zxf httpd-2.2.15.tar.gz -C /usr/src 配置阶 ...

  7. fluent中interpolate的用法

    原视频下载地址: https://pan.baidu.com/s/1hTD6tIlYL1S0nm30riAD9w 密码: ngv9

  8. ciscn2019华北赛区半决赛day1web5CyberPunk

    刚比赛完的一段时间期末考试云集,没有时间复现题目.趁着假期,争取多复现几道题. 复现平台 buuoj.cn 解题过程 首先进入题目页面 看起来没有什么特别的,就是一个可以提交信息的页面.查看响应报文也 ...

  9. Set详解

    Set集合: 元素不可重复 hashCode 特点:速度快,数组->链表->红黑树 set集合报错元素唯一: 存储元素(String,Interger,....Student,Person ...

  10. Oracle语法 及 SQL题目(一)

    目录 课例复制 SQL题目一 SQL题目二 SQL题目三 笔记 课例复制 OCM 全称:Oracle Certified Master 认证大师 含义:Oracle 原厂推出的数据库方向最高级别认证 ...