目的

在《GStreamer基础教程——流》里面我们展示了如何在较差的网络条件下使用缓冲这个机制来提升用户体验。本教程在《GStreamer基础教程——流》的基础上在扩展了一下,增加了把流的内容在本地存储。并且展示了:

如何开启既看式下载

如何知道下载的是什么

如何知道在哪里下载

如何限制下载数据的总量

介绍

当播放流的时候,从网络上获得的数据被锁住之后,会创建称为future-data的一个小的缓冲区。然而,在数据播放渲染之后就会被丢弃。这就意味着,如果用户想要倒回前面去看,相应地数据仍然需要再次下载。

像YouTube一样,播放流时播放器往往会裁剪,通常会把所有下载的数据都在本地保存,还会提供一个图形化的界面来显示已经下载了多少内容。

playbin2通过DOWNLOAD标志提供了一个比较类似的功能,它会把数据在本次临时保存起来用于在播放已经下载的部分时可以保持顺畅。

代码里面同时展示了如何使用缓冲查询,它可以让你知道哪部分的文件已经可用了。

一个适应网络并在本地存储数据的例子

[objc] view
plain
 copy

  1. <span style="font-size:14px;">#include <gst/gst.h>
  2. #include <string.h>
  3. #define GRAPH_LENGTH 80
  4. /* playbin2 flags */
  5. typedef enum {
  6. << 7) /* Enable progressive download (on selected formats) */
  7. } GstPlayFlags;
  8. typedef struct _CustomData {
  9. gboolean is_live;
  10. GstElement *pipeline;
  11. GMainLoop *loop;
  12. gint buffering_level;
  13. } CustomData;
  14. static void got_location (GstObject *gstobject, GstObject *prop_object, GParamSpec *prop, gpointer data) {
  15. gchar *location;
  16. g_object_get (G_OBJECT (prop_object), "temp-location", &location, NULL);
  17. g_print ("Temporary file: %s\n", location);
  18. /* Uncomment this line to keep the temporary file after the program exits */
  19. /* g_object_set (G_OBJECT (prop_object), "temp-remove", FALSE, NULL); */
  20. }
  21. static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {
  22. switch (GST_MESSAGE_TYPE (msg)) {
  23. case GST_MESSAGE_ERROR: {
  24. GError *err;
  25. gchar *debug;
  26. gst_message_parse_error (msg, &err, &debug);
  27. g_print ("Error: %s\n", err->message);
  28. g_error_free (err);
  29. g_free (debug);
  30. gst_element_set_state (data->pipeline, GST_STATE_READY);
  31. g_main_loop_quit (data->loop);
  32. break;
  33. }
  34. case GST_MESSAGE_EOS:
  35. /* end-of-stream */
  36. gst_element_set_state (data->pipeline, GST_STATE_READY);
  37. g_main_loop_quit (data->loop);
  38. break;
  39. case GST_MESSAGE_BUFFERING:
  40. /* If the stream is live, we do not care about buffering. */
  41. if (data->is_live) break;
  42. gst_message_parse_buffering (msg, &data->buffering_level);
  43. /* Wait until buffering is complete before start/resume playing */
  44. 00)
  45. gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
  46. else
  47. gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
  48. break;
  49. case GST_MESSAGE_CLOCK_LOST:
  50. /* Get a new clock */
  51. gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
  52. gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
  53. break;
  54. default:
  55. /* Unhandled message */
  56. break;
  57. }
  58. }
  59. static gboolean refresh_ui (CustomData *data) {
  60. GstQuery *query;
  61. gboolean result;
  62. query = gst_query_new_buffering (GST_FORMAT_PERCENT);
  63. result = gst_element_query (data->pipeline, query);
  64. if (result) {
  65. gint n_ranges, range, i;
  66. gchar graph[GRAPH_LENGTH + 1];
  67. GstFormat format = GST_FORMAT_TIME;
  68. 4 position = 0, duration = 0;
  69. memset (graph, ' ', GRAPH_LENGTH);
  70. graph[GRAPH_LENGTH] = '\0';
  71. n_ranges = gst_query_get_n_buffering_ranges (query);
  72. ; range < n_ranges; range++) {
  73. 4 start, stop;
  74. gst_query_parse_nth_buffering_range (query, range, &start, &stop);
  75. 00;
  76. 00;
  77. for (i = (gint)start; i < stop; i++)
  78. graph [i] = '-';
  79. }
  80. if (gst_element_query_position (data->pipeline, &format, &position) &&
  81. GST_CLOCK_TIME_IS_VALID (position) &&
  82. gst_element_query_duration (data->pipeline, &format, &duration) &&
  83. GST_CLOCK_TIME_IS_VALID (duration)) {
  84. ));
  85. 00 ? 'X' : '>';
  86. }
  87. g_print ("[%s]", graph);
  88. 00) {
  89. g_print (" Buffering: %3d%%", data->buffering_level);
  90. } else {
  91. g_print ("                ");
  92. }
  93. g_print ("\r");
  94. }
  95. return TRUE;
  96. }
  97. int main(int argc, charchar *argv[]) {
  98. GstElement *pipeline;
  99. GstBus *bus;
  100. GstStateChangeReturn ret;
  101. GMainLoop *main_loop;
  102. CustomData data;
  103. guint flags;
  104. /* Initialize GStreamer */
  105. gst_init (&argc, &argv);
  106. /* Initialize our data structure */
  107. , sizeof (data));
  108. 00;
  109. /* Build the pipeline */
  110. pipeline = gst_parse_launch ("playbin2 uri=http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
  111. bus = gst_element_get_bus (pipeline);
  112. /* Set the download flag */
  113. g_object_get (pipeline, "flags", &flags, NULL);
  114. flags |= GST_PLAY_FLAG_DOWNLOAD;
  115. g_object_set (pipeline, "flags", flags, NULL);
  116. /* Uncomment this line to limit the amount of downloaded data */
  117. /* g_object_set (pipeline, "ring-buffer-max-size", (guint64)4000000, NULL); */
  118. /* Start playing */
  119. ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  120. if (ret == GST_STATE_CHANGE_FAILURE) {
  121. g_printerr ("Unable to set the pipeline to the playing state.\n");
  122. gst_object_unref (pipeline);
  123. ;
  124. } else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
  125. data.is_live = TRUE;
  126. }
  127. main_loop = g_main_loop_new (NULL, FALSE);
  128. data.loop = main_loop;
  129. data.pipeline = pipeline;
  130. gst_bus_add_signal_watch (bus);
  131. g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data);
  132. g_signal_connect (pipeline, "deep-notify::temp-location", G_CALLBACK (got_location), NULL);
  133. /* Register a function that GLib will call every second */
  134. , (GSourceFunc)refresh_ui, &data);
  135. g_main_loop_run (main_loop);
  136. /* Free resources */
  137. g_main_loop_unref (main_loop);
  138. gst_object_unref (bus);
  139. gst_element_set_state (pipeline, GST_STATE_NULL);
  140. gst_object_unref (pipeline);
  141. g_print ("\n");
  142. ;
  143. }</span>

工作流程

这份代码是基于《GStreamer基础教程——流》里面例子的,我们仅仅看一下不同的地方即可。

创建

[objc] view
plain
 copy

  1. <span style="font-size:14px;">  /* Set the download flag */
  2. g_object_get (pipeline, "flags", &flags, NULL);
  3. flags |= GST_PLAY_FLAG_DOWNLOAD;
  4. g_object_set (pipeline, "flags", flags, NULL);</span>

通过设置这个标志,playbin2通知它内部的queue存储所有下载的数据。

[objc] view
plain
 copy

  1. <span style="font-size:14px;">g_signal_connect (pipeline, "deep-notify::temp-location", G_CALLBACK (got_location), NULL);</span>

当它们的子element属性发生变化时,playbin2就会发出deep-notify信号。在这里我们希望知道temp-location属性是什么时候变化的,了解queue2会把下载的数据存在哪里。

[objc] view
plain
 copy

  1. <span style="font-size:14px;">static void got_location (GstObject *gstobject, GstObject *prop_object, GParamSpec *prop, gpointer data) {
  2. gchar *location;
  3. g_object_get (G_OBJECT (prop_object), "temp-location", &location, NULL);
  4. g_print ("Temporary file: %s\n", location);
  5. /* Uncomment this line to keep the temporary file after the program exits */
  6. /* g_object_set (G_OBJECT (prop_object), "temp-remove", FALSE, NULL); */
  7. }</span>

这个temp-location属性是从发出信号的element那里获得的并打印出来。

当pipeline状态从PAUSED切换到READY时,这个文件会被删除。正如注释里面写的那样,你可以通过设置queue2的temp-remove属性位FALSE来保留下载的数据。

UI

在main函数里我们启动了一个1s的定时器,这样可以每秒刷新一下UI界面。

[objc] view
plain
 copy

  1. /* Register a function that GLib will call every second */
  2. , (GSourceFunc)refresh_ui, &data);

refresh_ui方法会查询pipeline来了解当前下载的数据在文件的位置以及当前播放的位置。并且用一种动画的方式在屏幕上显示出来。

[objc] view
plain
 copy

  1. [---->-------                ]

这里的'-'代表的是下载的部分,'>'代表的是当前播放的位置(当暂停的时候变成'X'位置)。当你的网络速度足够快得时候你可能会看不到下载的动画,在开始的时候就下载结束了。

[objc] view
plain
 copy

  1. static gboolean refresh_ui (CustomData *data) {
  2. GstQuery *query;
  3. gboolean result;
  4. query = gst_query_new_buffering (GST_FORMAT_PERCENT);
  5. result = gst_element_query (data->pipeline, query);

我们在refresh_ui里面做的第一件事是就是用gst_query_new_buffering()创建一个GstQuery对象,并用gst_element_query()传给playbin2。在《GStreamer基础教程04——时间管理》里面我们展示了如何用明确的方法来查询位置/播放总时间等,如果要查询更复杂一些的内容(比如缓冲),那么我们会用更通用的gst_element_query()方法。

缓冲的查询可以基于不同的GstFormat,并非所有的element都可以响应所有格式的查询,所以需要检查在pipeline里支持哪些格式。如果gst_element_query()返回TRUE,那么查询是成功的。查询的结果用GstQuery封装起来,可以用下面的方法来解析:

[objc] view
plain
 copy

  1. n_ranges = gst_query_get_n_buffering_ranges (query);
  2. ; range < n_ranges; range++) {
  3. 4 start, stop;
  4. gst_query_parse_nth_buffering_range (query, range, &start, &stop);
  5. 00;
  6. 00;
  7. for (i = (gint)start; i < stop; i++)
  8. graph [i] = '-';
  9. }

数据并不需要保证被按照顺序从头开始下载,因为跳跃播放时会让下载从一个新的地方开始。因此,gst_query_get_n_buffering_ranges()返回下载块的数目或者范围,然后我们用gst_query_parse_nth_buffering_rang()方法来解析下载块的位置和大小。

我们在调用gst_query_new_buffering()的请求会决定返回数据的格式,在这个例子里面,返回值是比例。这些查询到得数据用来绘制UI的下载动画。

[objc] view
plain
 copy

  1. if (gst_element_query_position (data->pipeline, &format, &position) &&
  2. GST_CLOCK_TIME_IS_VALID (position) &&
  3. gst_element_query_duration (data->pipeline, &format, &duration) &&
  4. GST_CLOCK_TIME_IS_VALID (duration)) {
  5. ));
  6. 00 ? 'X' : '>';
  7. }

下一步就是当前位置的查询。它也支持比例的格式,所以代码和前面应该比较类似。不过这部分目前支持不是很好,所以我们使用了时间这个格式。

当前位置使用'>'或者'X'来表示,如果缓冲不到100%,cb_message会让pipeline处于PAUSE状态,那样我们就显示'X',如果已经满了100%,那么pipeline就在PLAYING状态,我们就显示'>'。

[objc] view
plain
 copy

  1. 00) {
  2. g_print (" Buffering: %3d%%", data->buffering_level);
  3. } else {
  4. g_print ("                ");
  5. }

最后,如果缓冲时钟小于100%,我们就把这个数据显示出来。

限制下载文件的大小

[objc] view
plain
 copy

  1. /* Uncomment this line to limit the amount of downloaded data */
  2. /* g_object_set (pipeline, "ring-buffer-max-size", (guint64)4000000, NULL); */

打开139行的注释,让我们看看这个是如何做到的。缩小临时文件的大小,这样播放过的区域就会被覆盖。

最新文章

  1. kettle系列-[KettleUtil]kettle插件,类似kettle的自定义java类控件
  2. php5.2.3连接sqlserver2008
  3. Useful links
  4. android wifi Direct Audio TX/RX延迟分析
  5. eclipse 代码自动提示
  6. bzoj 1314: River过河 优先队列
  7. JAVA三大框架的各自作用
  8. windows平台发消息到非UI线程.
  9. 使用autoconf和automake生成Makefile文件(转)
  10. git和SVN的区别
  11. phantomjs submit click
  12. angular指令之complie和link不得不说的故事
  13. 黑马day16 jquery&amp;amp;内容过滤选择器&amp;amp;可见度选择器
  14. Android动态改变App在Launcher里面的icon
  15. PHP文件域上传
  16. Sequential Container
  17. HDU 3980 Paint Chain (sg函数)
  18. UI设计,使用感知分层技术
  19. 一行转多行 及多行转一行的 hive语句
  20. zookeeper集群环境搭建详细图文教程

热门文章

  1. 如何在Eclipse中写Processing的sketch
  2. fedora安装设置
  3. Angular动态组件
  4. LOJ2719. 「NOI2018」冒泡排序 [组合计数]
  5. 深度Linux /etc/profile 环境变量生效问题
  6. 升级springboot导致的业务异步回调积压问题定位
  7. IDEA控制台乱码终极解决方案
  8. De1ctf - shell shell shell记录
  9. 关于 array of const
  10. Spring 中开启Mybatis缓存