mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 19:20:35 +00:00
291 lines
11 KiB
Markdown
291 lines
11 KiB
Markdown
|
# GStreamer SDK documentation : Basic tutorial 12: Streaming
|
|||
|
|
|||
|
This page last changed on Sep 28, 2012 by xartigas.
|
|||
|
|
|||
|
# Goal
|
|||
|
|
|||
|
Playing media straight from the Internet without storing it locally is
|
|||
|
known as Streaming. We have been doing it throughout the tutorials
|
|||
|
whenever we used a URI starting with `http://`. This tutorial shows a
|
|||
|
couple of additional points to keep in mind when streaming. In
|
|||
|
particular:
|
|||
|
|
|||
|
- How to enable buffering (to alleviate network problems)
|
|||
|
- How to recover from interruptions (lost clock)
|
|||
|
|
|||
|
# Introduction
|
|||
|
|
|||
|
When streaming, media chunks are decoded and queued for presentation as
|
|||
|
soon as they arrive form the network. This means that if a chunk is
|
|||
|
delayed (which is not an uncommon situation at all on the Internet) the
|
|||
|
presentation queue might run dry and media playback could stall.
|
|||
|
|
|||
|
The universal solution is to build a “buffer”, this is, allow a certain
|
|||
|
number of media chunks to be queued before starting playback. In this
|
|||
|
way, playback start is delayed a bit, but, if some chunks are late,
|
|||
|
reproduction is not impacted as there are more chunks in the queue,
|
|||
|
waiting.
|
|||
|
|
|||
|
As it turns out, this solution is already implemented in GStreamer, but
|
|||
|
the previous tutorials have not been benefiting from it. Some elements,
|
|||
|
like the `queue2` and `multiqueue` found inside `playbin2`, are capable
|
|||
|
of building this buffer and post bus messages regarding the buffer level
|
|||
|
(the state of the queue). An application wanting to have more network
|
|||
|
resilience, then, should listen to these messages and pause playback if
|
|||
|
the buffer level is not high enough (usually, whenever it is below
|
|||
|
100%).
|
|||
|
|
|||
|
To achieve synchronization among multiple sinks (for example and audio
|
|||
|
and a video sink) a global clock is used. This clock is selected by
|
|||
|
GStreamer among all elements which can provide one. Under some
|
|||
|
circumstances, for example, an RTP source switching streams or changing
|
|||
|
the output device, this clock can be lost and a new one needs to be
|
|||
|
selected. This happens mostly when dealing with streaming, so the
|
|||
|
process is explained in this tutorial.
|
|||
|
|
|||
|
When the clock is lost, the application receives a message on the bus;
|
|||
|
to select a new one, the application just needs to set the pipeline to
|
|||
|
PAUSED and then to PLAYING again.
|
|||
|
|
|||
|
# A network-resilient example
|
|||
|
|
|||
|
Copy this code into a text file named `basic-tutorial-12.c`.
|
|||
|
|
|||
|
<table>
|
|||
|
<tbody>
|
|||
|
<tr class="odd">
|
|||
|
<td><img src="images/icons/emoticons/information.png" width="16" height="16" /></td>
|
|||
|
<td><p>This tutorial is included in the SDK since release 2012.7. If you cannot find it in the downloaded code, please install the latest release of the GStreamer SDK.</p></td>
|
|||
|
</tr>
|
|||
|
</tbody>
|
|||
|
</table>
|
|||
|
|
|||
|
**basic-tutorial-12.c**
|
|||
|
|
|||
|
``` theme: Default; brush: cpp; gutter: true
|
|||
|
#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 ("playbin2 uri=http://docs.gstreamer.com/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;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
<table>
|
|||
|
<tbody>
|
|||
|
<tr class="odd">
|
|||
|
<td><img src="images/icons/emoticons/information.png" width="16" height="16" /></td>
|
|||
|
<td><div id="expander-8150053" class="expand-container">
|
|||
|
<div id="expander-control-8150053" class="expand-control">
|
|||
|
<span class="expand-control-icon"><img src="images/icons/grey_arrow_down.gif" class="expand-control-image" /></span><span class="expand-control-text">Need help? (Click to expand)</span>
|
|||
|
</div>
|
|||
|
<div id="expander-content-8150053" class="expand-content">
|
|||
|
<p>If you need help to compile this code, refer to the <strong>Building the tutorials</strong> section for your platform: <a href="Installing%2Bon%2BLinux.html#InstallingonLinux-Build">Linux</a>, <a href="Installing%2Bon%2BMac%2BOS%2BX.html#InstallingonMacOSX-Build">Mac OS X</a> or <a href="Installing%2Bon%2BWindows.html#InstallingonWindows-Build">Windows</a>, or use this specific command on Linux:</p>
|
|||
|
<div class="panel" style="border-width: 1px;">
|
|||
|
<div class="panelContent">
|
|||
|
<p><code>gcc basic-tutorial-10.c -o basic-tutorial-10 `pkg-config --cflags --libs gstreamer-0.10`</code></p>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<p>If you need help to run this code, refer to the <strong>Running the tutorials</strong> section for your platform: <a href="Installing%2Bon%2BLinux.html#InstallingonLinux-Run">Linux</a>, <a href="Installing%2Bon%2BMac%2BOS%2BX.html#InstallingonMacOSX-Run">Mac OS X</a> or <a href="Installing%2Bon%2BWindows.html#InstallingonWindows-Run">Windows</a></p>
|
|||
|
<p>This tutorial opens a window and displays a movie, with accompanying audio. The media is fetched from the Internet, so the window might take a few seconds to appear, depending on your connection speed. In the console window, you should see a buffering message, and playback should only start when the buffering reaches 100%. This percentage might not change at all if your connection is fast enough and buffering is not required.</p>
|
|||
|
<p>Required libraries: <code>gstreamer-0.10</code></p>
|
|||
|
</div>
|
|||
|
</div></td>
|
|||
|
</tr>
|
|||
|
</tbody>
|
|||
|
</table>
|
|||
|
|
|||
|
# Walkthrough
|
|||
|
|
|||
|
The only special thing this tutorial does is react to certain messages;
|
|||
|
therefore, the initialization code is very simple and should be
|
|||
|
self-explanative by now. The only new bit is the detection of live
|
|||
|
streams:
|
|||
|
|
|||
|
``` first-line: 74; theme: Default; brush: cpp; gutter: true
|
|||
|
/* 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;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Live streams cannot be paused, so they behave in PAUSED state as if they
|
|||
|
were in the PLAYING state. Setting live streams to PAUSED succeeds, but
|
|||
|
returns `GST_STATE_CHANGE_NO_PREROLL`, instead of
|
|||
|
`GST_STATE_CHANGE_SUCCESS` to indicate that this is a live stream. We
|
|||
|
are receiving the NO\_PROROLL return code even though we are trying to
|
|||
|
set the pipeline to PLAYING, because state changes happen progressively
|
|||
|
(from NULL to READY, to PAUSED and then to PLAYING).
|
|||
|
|
|||
|
We care about live streams because we want to disable buffering for
|
|||
|
them, so we take note of the result of `gst_element_set_state()` in the
|
|||
|
`is_live` variable.
|
|||
|
|
|||
|
Let’s now review the interesting parts of the message parsing callback:
|
|||
|
|
|||
|
``` first-line: 31; theme: Default; brush: cpp; gutter: true
|
|||
|
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;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
First, if this is a live source, ignore buffering messages.
|
|||
|
|
|||
|
We parse the buffering message with `gst_message_parse_buffering()` to
|
|||
|
retrieve the buffering level.
|
|||
|
|
|||
|
Then, we print the buffering level on the console and set the pipeline
|
|||
|
to PAUSED if it is below 100%. Otherwise, we set the pipeline to
|
|||
|
PLAYING.
|
|||
|
|
|||
|
At startup, we will see the buffering level rise up to 100% before
|
|||
|
playback starts, which is what we wanted to achieve. If, later on, the
|
|||
|
network becomes slow or unresponsive and our buffer depletes, we will
|
|||
|
receive new buffering messages with levels below 100% so we will pause
|
|||
|
the pipeline again until enough buffer has been built up.
|
|||
|
|
|||
|
``` theme: Default; brush: cpp; gutter: false
|
|||
|
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;
|
|||
|
```
|
|||
|
|
|||
|
For the second network issue, the loss of clock, we simply set the
|
|||
|
pipeline to PAUSED and back to PLAYING, so a new clock is selected,
|
|||
|
waiting for new media chunks to be received if necessary.
|
|||
|
|
|||
|
# Conclusion
|
|||
|
|
|||
|
This tutorial has described how to add network resilience to your
|
|||
|
application with two very simple precautions:
|
|||
|
|
|||
|
- Taking care of buffering messages sent by the pipeline
|
|||
|
- Taking care of clock loss
|
|||
|
|
|||
|
Handling these messages improves the application’s response to network
|
|||
|
problems, increasing the overall playback smoothness.
|
|||
|
|
|||
|
It has been a pleasure having you here, and see you soon\!
|
|||
|
|
|||
|
## Attachments:
|
|||
|
|
|||
|
![](images/icons/bullet_blue.gif)
|
|||
|
[basic-tutorial-12.c](attachments/327806/2424843.c) (text/plain)
|
|||
|
![](images/icons/bullet_blue.gif)
|
|||
|
[vs2010.zip](attachments/327806/2424844.zip) (application/zip)
|
|||
|
|
|||
|
Document generated by Confluence on Oct 08, 2015 10:27
|
|||
|
|