基础教程12:Streaming (流媒体)
目标
直接从互联网播放媒体而不将其本地存储称为流媒体。我们在教程中一直使用以http://开头的URI进行流媒体播放。本教程展示了在流媒体播放时需要注意的一些额外要点,特别是:
- 如何启用缓冲(以缓解网络问题)
- 如何处理中断(丢失时钟)
介绍
在流媒体播放时,媒体数据块解码后立即排队等待呈现。这意味着,如果某个数据块延迟(在互联网上非常常见的情况),则呈现队列可能会耗尽,导致媒体播放停滞。
通用解决方案是构建一个“缓冲区”,即允许在开始播放之前将一定数量的媒体数据块排队。这样,虽然播放开始会稍有延迟,但如果某些数据块延迟,由于队列中有更多的数据块等待,播放不会受到影响。
事实证明,这个解决方案已经在GStreamer中实现,但之前的教程并没有从中受益。一些元素,如playbin中的queue2和multiqueue,能够构建这个缓冲区,并通过总线消息报告缓冲区级别(队列的状态)。因此,希望具有更强网络恢复能力的应用应该监听这些消息,并在缓冲区级别不足以支持播放时(通常是低于100%时)暂停播放。
为了实现多个接收器(例如音频和视频接收器)之间的同步,GStreamer使用全局时钟。GStreamer在所有可以提供时钟的元素中选择一个时钟。在某些情况下,例如RTP源切换流或更改输出设备,可能会丢失此时钟并需要选择一个新的时钟。这主要发生在处理流媒体时,因此本教程对此过程进行了解释。
当失去时钟时,应用程序会在总线上收到一条消息;要选择一个新时钟,应用程序只需将管道设置为PAUSED,然后再设置为PLAYING即可。
network-resilient示例
将此代码复制到名为basic-tutorial-12.c
文本文件中。
basic-tutorial-12.c
#include <gst/gst.h>
#include <string.h>
typedef struct _CustomData {
gboolean is_live;
GstElement *pipeline;
GMainLoop *loop;
} CustomData;
static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: {
GError *err;
gchar *debug;
gst_message_parse_error (msg, &err, &debug);
g_print ("Error: %s\n", err->message);
g_error_free (err);
g_free (debug);
gst_element_set_state (data->pipeline, GST_STATE_READY);
g_main_loop_quit (data->loop);
break;
}
case GST_MESSAGE_EOS:
/* end-of-stream */
gst_element_set_state (data->pipeline, GST_STATE_READY);
g_main_loop_quit (data->loop);
break;
case GST_MESSAGE_BUFFERING: {
gint percent = 0;
/* If the stream is live, we do not care about buffering. */
if (data->is_live) break;
gst_message_parse_buffering (msg, &percent);
g_print ("Buffering (%3d%%)\r", percent);
/* Wait until buffering is complete before start/resume playing */
if (percent < 100)
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
else
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
}
case GST_MESSAGE_CLOCK_LOST:
/* Get a new clock */
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
default:
/* Unhandled message */
break;
}
}
int main(int argc, char *argv[]) {
GstElement *pipeline;
GstBus *bus;
GstStateChangeReturn ret;
GMainLoop *main_loop;
CustomData data;
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Initialize our data structure */
memset (&data, 0, sizeof (data));
/* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);
bus = gst_element_get_bus (pipeline);
/* Start playing */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr ("Unable to set the pipeline to the playing state.\n");
gst_object_unref (pipeline);
return -1;
} else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
data.is_live = TRUE;
}
main_loop = g_main_loop_new (NULL, FALSE);
data.loop = main_loop;
data.pipeline = pipeline;
gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data);
g_main_loop_run (main_loop);
/* Free resources */
g_main_loop_unref (main_loop);
gst_object_unref (bus);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
return 0;
}
需要帮忙吗?
如果您需要帮助来编译此代码,请参阅为您的平台构建教程部分:Linux、Mac OS X或Windows,或在Linux上使用此特定命令:
gcc basic-tutorial-12.c -o basic-tutorial-12 `pkg-config --cflags --libs gstreamer-1.0`
如果您需要帮助来运行此代码,请参阅为您的平台运行教程部分:Linux、Mac OS X或Windows。
本教程将打开一个窗口,并显示带有音频的电影。媒体是从Internet获取的,因此该窗口可能需要几秒钟才能出现,具体取决于您的连接速度。在控制台窗口中,您应该会看到一条缓冲消息,并且只有当缓冲达到100%时才会开始播放。如果您的连接速度足够快并且不需要缓冲,则此百分比可能根本不会改变。
所需库:
gstreamer-1.0
工作流
这个教程唯一特别的地方是它对某些消息做出反应;因此,初始化代码非常简单,现在应该可以自己解释了。唯一的新内容是实时流的检测:
/* Start playing */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr ("Unable to set the pipeline to the playing state.\n");
gst_object_unref (pipeline);
return -1;
} else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
data.is_live = TRUE;
}
由于直播流无法暂停,因此它们在PAUSED状态下的行为与PLAYING状态一样。将直播流设置为PAUSED会成功,但会返回GST_STATE_CHANGE_NO_PREROLL而不是GST_STATE_CHANGE_SUCCESS,以指示这是一个直播流。即使我们尝试将管道设置为PLAYING,也会收到NO_PREROLL返回代码,因为状态更改是逐步进行的(从NULL到READY,再到PAUSED,然后到PLAYING)。
我们关心直播流是因为我们希望禁用它们的缓冲区,所以我们在is_live变量中记录gst_element_set_state()的结果。
现在让我们回顾消息解析回调的有趣部分:
case GST_MESSAGE_BUFFERING: {
gint percent = 0;
/* If the stream is live, we do not care about buffering. */
if (data->is_live) break;
gst_message_parse_buffering (msg, &percent);
g_print ("Buffering (%3d%%)\r", percent);
/* Wait until buffering is complete before start/resume playing */
if (percent < 100)
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
else
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
}
首先,如果这是一个实时源,请忽略缓冲消息。
我们使用gst_message_parse_buffering()解析缓冲消息以检索缓冲级别。
然后,我们在控制台上打印缓冲级别,如果它低于100%,则将管道设置为PAUSED。否则,我们将管道设置为PLAYING。
在启动时,我们会看到缓冲级别上升到100%后才开始播放,这正是我们想要实现的效果。如果稍后网络变慢或无响应,我们的缓冲区耗尽,我们将收到新的缓冲级别低于100%的缓冲消息,因此我们将再次暂停管道,直到建立了足够的缓冲区。
case GST_MESSAGE_CLOCK_LOST:
/* Get a new clock */
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
对于第二个网络问题,时钟丢失,我们只需将管道设置为PAUSED并返回到PLAYING,以便选择一个新的时钟,等待接收新的媒体块(如果需要的话)。
结论
本教程介绍了如何通过两个非常简单的预防措施为您的应用程序添加网络韧性:
- 处理管道发送的缓冲消息
- 处理时钟丢失
处理这些消息可以提高应用程序对网络问题的响应能力,从而提高整体播放流畅度。