dashdemux: Replace GQueue by GstDataQueue

GstDataQueue has proper locking and provides functions to limit the
size of the queue. Also has blocking calls that are useful to
our multithread scenario in Dash.
This commit is contained in:
Thiago Santos 2013-01-28 18:52:04 -03:00
parent 27b1abbda3
commit 7330225ac8
5 changed files with 135 additions and 239 deletions

View file

@ -216,7 +216,6 @@ static gboolean gst_dash_demux_get_next_fragment_set (GstDashDemux * demux);
static void gst_dash_demux_reset (GstDashDemux * demux, gboolean dispose);
static GstClockTime gst_dash_demux_get_buffering_time (GstDashDemux * demux);
static float gst_dash_demux_get_buffering_ratio (GstDashDemux * demux);
static GstBuffer *gst_dash_demux_merge_buffer_list (GstFragment * fragment);
static GstCaps *gst_dash_demux_get_input_caps (GstDashDemux * demux,
GstActiveStream * stream);
static GstClockTime gst_dash_demux_stream_get_buffering_time (GstDashDemuxStream
@ -463,7 +462,7 @@ gst_dash_demux_all_queues_have_data (GstDashDemux * demux)
for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data;
if (g_queue_is_empty (stream->queue)) {
if (gst_data_queue_is_empty (stream->queue)) {
return FALSE;
}
}
@ -477,14 +476,42 @@ gst_dash_demux_clear_queues (GstDashDemux * demux)
for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data;
while (!g_queue_is_empty (stream->queue)) {
GstFragment *fragment = g_queue_pop_head (stream->queue);
g_object_unref (fragment);
}
g_queue_clear (stream->queue);
gst_data_queue_flush (stream->queue);
}
}
static gboolean
_check_queue_full (GstDataQueue * q, guint visible, guint bytes, guint64 time,
GstDashDemuxStream * stream)
{
/* TODO add limits */
return FALSE;
}
static void
_data_queue_item_destroy (GstDataQueueItem * item)
{
gst_mini_object_unref (item->object);
g_free (item);
}
static void
gst_dash_demux_stream_push_data (GstDashDemuxStream * stream,
GstBuffer * fragment)
{
GstDataQueueItem *item = g_new0 (GstDataQueueItem, 1);
item->object = GST_MINI_OBJECT_CAST (fragment);
item->duration = GST_BUFFER_DURATION (fragment);
item->visible = TRUE;
item->size = GST_BUFFER_SIZE (fragment);
item->destroy = (GDestroyNotify) _data_queue_item_destroy;
gst_data_queue_push (stream->queue, item);
}
static gboolean
gst_dash_demux_src_event (GstPad * pad, GstEvent * event)
{
@ -563,8 +590,8 @@ gst_dash_demux_src_event (GstPad * pad, GstEvent * event)
chunk = list->data;
current_pos = chunk->start_time;
//current_sequence = chunk->number;
GST_WARNING_OBJECT (demux, "%i <= %i (%i)", current_pos, target_pos,
chunk->duration);
GST_WARNING_OBJECT (demux, "%llu <= %llu (%llu)", current_pos,
target_pos, chunk->duration);
if (current_pos <= target_pos
&& target_pos < current_pos + chunk->duration) {
break;
@ -626,6 +653,10 @@ gst_dash_demux_src_event (GstPad * pad, GstEvent * event)
/* Restart the demux */
demux->cancelled = FALSE;
demux->end_of_manifest = FALSE;
for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data;
gst_data_queue_set_flushing (stream->queue, FALSE);
}
gst_dash_demux_resume_download_task (demux);
gst_dash_demux_resume_stream_task (demux);
g_static_rec_mutex_unlock (&demux->stream_lock);
@ -686,7 +717,9 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux)
stream = g_new0 (GstDashDemuxStream, 1);
caps = gst_dash_demux_get_input_caps (demux, active_stream);
stream->queue = g_queue_new ();
stream->queue =
gst_data_queue_new ((GstDataQueueCheckFullFunction) _check_queue_full,
stream);
stream->index = i;
stream->input_caps = caps;
@ -878,7 +911,14 @@ gst_dash_demux_pad (GstPad * pad, GstBuffer * buf)
static void
gst_dash_demux_stop (GstDashDemux * demux)
{
GSList *iter;
gst_uri_downloader_cancel (demux->downloader);
for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data;
gst_data_queue_set_flushing (stream->queue, TRUE);
}
if (GST_TASK_STATE (demux->download_task) != GST_TASK_STOPPED) {
GST_TASK_SIGNAL (demux->download_task);
@ -969,16 +1009,18 @@ needs_pad_switch (GstDashDemux * demux)
GSList *iter;
for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDataQueueItem *item;
GstDashDemuxStream *stream = iter->data;
GstFragment *newFragment = g_queue_peek_head (stream->queue);
GstCaps *srccaps = NULL;
GstBuffer *buffer;
if (newFragment == NULL) {
if (!gst_data_queue_peek (stream->queue, &item))
continue;
}
if (stream->output_caps)
gst_caps_unref (stream->output_caps);
stream->output_caps = gst_fragment_get_caps (newFragment);
buffer = GST_BUFFER_CAST (item->object);
gst_caps_replace (&stream->output_caps, GST_BUFFER_CAPS (buffer));
if (G_LIKELY (stream->pad))
srccaps = gst_pad_get_negotiated_caps (stream->pad);
if (G_UNLIKELY (!srccaps
@ -1017,7 +1059,6 @@ static void
gst_dash_demux_stream_loop (GstDashDemux * demux)
{
GstFlowReturn ret;
GstBufferList *buffer_list;
guint nb_adaptation_set = 0;
GstActiveStream *active_stream;
gboolean switch_pad;
@ -1057,15 +1098,18 @@ gst_dash_demux_stream_loop (GstDashDemux * demux)
}
for (iter = demux->streams, i = 0; iter; i++, iter = g_slist_next (iter)) {
GstDataQueueItem *item;
GstBuffer *buffer;
GstDashDemuxStream *stream = iter->data;
GstFragment *fragment = g_queue_pop_head (stream->queue);
if (!fragment)
if (!gst_data_queue_pop (stream->queue, &item))
continue;
buffer = GST_BUFFER_CAST (item->object);
active_stream = gst_mpdparser_get_active_stream_by_index (demux->client, i);
if (demux->need_segment) {
GstClockTime start = fragment->start_time + demux->position_shift;
GstClockTime start =
GST_BUFFER_TIMESTAMP (buffer) + demux->position_shift;
/* And send a newsegment */
GST_DEBUG_OBJECT (demux, "Sending new-segment. segment start:%"
GST_TIME_FORMAT, GST_TIME_ARGS (start));
@ -1074,11 +1118,13 @@ gst_dash_demux_stream_loop (GstDashDemux * demux)
start, GST_CLOCK_TIME_NONE, start));
}
GST_DEBUG_OBJECT (demux, "Pushing fragment #%d (stream %i)",
fragment->index, i);
buffer_list = gst_fragment_get_buffer_list (fragment);
g_object_unref (fragment);
ret = gst_pad_push_list (stream->pad, buffer_list);
GST_DEBUG_OBJECT (demux,
"Pushing fragment %p #%d (stream %i) ts:%" GST_TIME_FORMAT " dur:%"
GST_TIME_FORMAT, buffer, GST_BUFFER_OFFSET (buffer), i,
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)));
ret = gst_pad_push (stream->pad, gst_buffer_ref (buffer));
item->destroy (item);
if ((ret != GST_FLOW_OK) && (active_stream->mimeType == GST_STREAM_VIDEO))
goto error_pushing;
}
@ -1121,7 +1167,7 @@ gst_dash_demux_stream_free (GstDashDemuxStream * stream)
gst_object_unref (stream->pad);
/* TODO flush the queue */
g_queue_free (stream->queue);
g_object_unref (stream->queue);
g_free (stream);
}
@ -1182,22 +1228,11 @@ gst_dash_demux_get_buffering_time (GstDashDemux * demux)
static GstClockTime
gst_dash_demux_stream_get_buffering_time (GstDashDemuxStream * stream)
{
GstFragment *first_fragment, *last_fragment;
GstClockTime buffer_time = 0;
GstDataQueueSize level;
/* get first fragment */
first_fragment = g_queue_peek_head (stream->queue);
/* get last fragment */
last_fragment = g_queue_peek_tail (stream->queue);
gst_data_queue_get_level (stream->queue, &level);
if (!first_fragment && !last_fragment)
return 0;
if (first_fragment && last_fragment) {
buffer_time = last_fragment->stop_time - first_fragment->start_time;
}
return buffer_time;
return (GstClockTime) level.time;
}
static float
@ -1210,34 +1245,6 @@ gst_dash_demux_get_buffering_ratio (GstDashDemux * demux)
return buffering_time / demux->min_buffering_time;
}
static GstBuffer *
gst_dash_demux_merge_buffer_list (GstFragment * fragment)
{
GstBufferList *list;
GstBufferListIterator *it;
GstBuffer *buffer, *ret = NULL;
GstAdapter *adapter;
gsize size;
adapter = gst_adapter_new ();
list = gst_fragment_get_buffer_list (fragment);
it = gst_buffer_list_iterate (list);
while (gst_buffer_list_iterator_next_group (it)) {
while ((buffer = gst_buffer_list_iterator_next (it)) != NULL) {
gst_adapter_push (adapter, gst_buffer_ref (buffer));
}
}
gst_buffer_list_iterator_free (it);
gst_buffer_list_unref (list);
size = gst_adapter_available (adapter);
if (size > 0)
ret = gst_adapter_take_buffer (adapter, size);
GST_DEBUG ("Extracted a buffer of size %d from the fragment", size);
g_object_unref (adapter);
return ret;
}
/* gst_dash_demux_download_loop:
*
* Loop for the "download' task that fetches fragments based on the
@ -1301,7 +1308,7 @@ gst_dash_demux_download_loop (GstDashDemux * demux)
"Failed to update the manifest file from URL %s",
demux->client->mpd_uri);
} else {
buffer = gst_dash_demux_merge_buffer_list (download);
buffer = gst_fragment_get_buffer (download);
g_object_unref (download);
/* parse the manifest file */
if (buffer == NULL) {
@ -1554,44 +1561,6 @@ gst_dash_demux_get_next_header (GstDashDemux * demux, guint stream_idx)
return fragment;
}
static GstBufferListItem
gst_dash_demux_add_buffer_cb (GstBuffer ** buffer,
guint group, guint idx, gpointer user_data)
{
GstFragment *frag = GST_FRAGMENT (user_data);
/* This buffer still belongs to the original fragment */
/* so we need to increase refcount */
gst_fragment_add_buffer (frag, gst_buffer_ref (*buffer));
return GST_BUFFER_LIST_CONTINUE;
}
/* Since we cannot add headers after the chunk has been downloaded, we have to recreate a new fragment */
static GstFragment *
gst_dash_demux_prepend_header (GstDashDemux * demux,
GstFragment * frag, GstFragment * header)
{
GstBufferList *list;
GstFragment *res = gst_fragment_new ();
res->name = g_strdup (frag->name);
res->download_start_time = frag->download_start_time;
res->download_stop_time = frag->download_stop_time;
res->start_time = frag->start_time;
res->stop_time = frag->stop_time;
res->index = frag->index;
res->discontinuous = frag->discontinuous;
list = gst_fragment_get_buffer_list (header);
gst_buffer_list_foreach (list, gst_dash_demux_add_buffer_cb, res);
gst_buffer_list_unref (list);
list = gst_fragment_get_buffer_list (frag);
gst_buffer_list_foreach (list, gst_dash_demux_add_buffer_cb, res);
gst_buffer_list_unref (list);
res->completed = TRUE;
return res;
}
static GstCaps *
gst_dash_demux_get_video_input_caps (GstDashDemux * demux,
GstActiveStream * stream)
@ -1749,6 +1718,7 @@ gst_dash_demux_get_next_fragment_set (GstDashDemux * demux)
for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data;
guint stream_idx = stream->index;
GstBuffer *buffer;
if (!gst_mpd_client_get_next_fragment (demux->client,
stream_idx, &discont, &next_fragment_uri, &duration, &timestamp)) {
@ -1758,7 +1728,10 @@ gst_dash_demux_get_next_fragment_set (GstDashDemux * demux)
}
GST_INFO_OBJECT (demux, "Next fragment for stream #%i", stream_idx);
GST_INFO_OBJECT (demux, "Fetching next fragment %s", next_fragment_uri);
GST_INFO_OBJECT (demux,
"Fetching next fragment %s ts:%" GST_TIME_FORMAT " dur:%"
GST_TIME_FORMAT, next_fragment_uri, GST_TIME_ARGS (timestamp),
GST_TIME_ARGS (duration));
download = gst_uri_downloader_fetch_uri (demux->downloader,
next_fragment_uri);
@ -1767,33 +1740,36 @@ gst_dash_demux_get_next_fragment_set (GstDashDemux * demux)
if (download == NULL)
return FALSE;
download->start_time = timestamp;
download->stop_time = timestamp + duration;
buffer = gst_fragment_get_buffer (download);
active_stream =
gst_mpdparser_get_active_stream_by_index (demux->client, stream_idx);
if (stream == NULL)
if (stream == NULL) /* TODO unref fragments */
return FALSE;
download->index = gst_mpd_client_get_segment_index (active_stream) - 1;
if (need_header) {
/* We need to fetch a new header */
if ((header = gst_dash_demux_get_next_header (demux, stream_idx)) == NULL) {
GST_INFO_OBJECT (demux, "Unable to fetch header");
} else {
GstBuffer *header_buffer;
/* Replace fragment with a new one including the header */
GstFragment *new_fragment =
gst_dash_demux_prepend_header (demux, download, header);
g_object_unref (header);
g_object_unref (download);
download = new_fragment;
header_buffer = gst_fragment_get_buffer (header);
buffer = gst_buffer_join (header_buffer, buffer);
}
}
gst_fragment_set_caps (download, stream->input_caps);
g_queue_push_tail (stream->queue, download);
size_buffer += gst_fragment_get_buffer_size (download);
buffer = gst_buffer_make_metadata_writable (buffer);
GST_BUFFER_TIMESTAMP (buffer) = timestamp;
GST_BUFFER_DURATION (buffer) = duration;
GST_BUFFER_OFFSET (buffer) =
gst_mpd_client_get_segment_index (active_stream) - 1;
gst_buffer_set_caps (buffer, stream->input_caps);
gst_dash_demux_stream_push_data (stream, buffer);
size_buffer += GST_BUFFER_SIZE (buffer);
}
/* Wake the download task up */

View file

@ -32,8 +32,7 @@
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include <gst/base/gstdataqueue.h>
#include "gstmpdparser.h"
#include "gstfragmented.h"
#include "gsturidownloader.h"
@ -53,7 +52,6 @@ G_BEGIN_DECLS
typedef struct _GstDashDemuxStream GstDashDemuxStream;
typedef struct _GstDashDemux GstDashDemux;
typedef struct _GstDashDemuxClass GstDashDemuxClass;
#define MAX_LANGUAGES 20
struct _GstDashDemuxStream
{
@ -64,7 +62,7 @@ struct _GstDashDemuxStream
GstCaps *output_caps;
GstCaps *input_caps;
GQueue *queue;
GstDataQueue *queue;
};
/**

View file

@ -20,7 +20,7 @@
*/
#include <glib.h>
#include <gst/base/gsttypefindhelper.h>
#include <gst/base/gstadapter.h>
#include "gstfragmented.h"
#include "gstfragment.h"
@ -35,18 +35,13 @@ enum
PROP_NAME,
PROP_DURATION,
PROP_DISCONTINOUS,
PROP_BUFFER_LIST,
PROP_CAPS,
PROP_LAST
};
struct _GstFragmentPrivate
{
GstBufferList *buffer_list;
guint64 size;
GstBufferListIterator *buffer_iterator;
GstCaps *caps;
GMutex lock;
GstAdapter *adapter;
GstBuffer *buffer;
};
G_DEFINE_TYPE (GstFragment, gst_fragment, G_TYPE_OBJECT);
@ -54,24 +49,6 @@ G_DEFINE_TYPE (GstFragment, gst_fragment, G_TYPE_OBJECT);
static void gst_fragment_dispose (GObject * object);
static void gst_fragment_finalize (GObject * object);
static void
gst_fragment_set_property (GObject * object,
guint property_id, const GValue * value, GParamSpec * pspec)
{
GstFragment *fragment = GST_FRAGMENT (object);
switch (property_id) {
case PROP_CAPS:
gst_fragment_set_caps (fragment, g_value_get_boxed (value));
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gst_fragment_get_property (GObject * object,
guint property_id, GValue * value, GParamSpec * pspec)
@ -95,14 +72,6 @@ gst_fragment_get_property (GObject * object,
g_value_set_boolean (value, fragment->discontinuous);
break;
case PROP_BUFFER_LIST:
g_value_set_object (value, gst_fragment_get_buffer_list (fragment));
break;
case PROP_CAPS:
g_value_set_boxed (value, gst_fragment_get_caps (fragment));
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@ -110,8 +79,6 @@ gst_fragment_get_property (GObject * object,
}
}
static void
gst_fragment_class_init (GstFragmentClass * klass)
{
@ -119,7 +86,6 @@ gst_fragment_class_init (GstFragmentClass * klass)
g_type_class_add_private (klass, sizeof (GstFragmentPrivate));
gobject_class->set_property = gst_fragment_set_property;
gobject_class->get_property = gst_fragment_get_property;
gobject_class->dispose = gst_fragment_dispose;
gobject_class->finalize = gst_fragment_finalize;
@ -140,16 +106,6 @@ gst_fragment_class_init (GstFragmentClass * klass)
g_object_class_install_property (gobject_class, PROP_DURATION,
g_param_spec_uint64 ("duration", "Fragment duration",
"Duration of the fragment", 0, G_MAXUINT64, 0, G_PARAM_READABLE));
g_object_class_install_property (gobject_class, PROP_BUFFER_LIST,
g_param_spec_object ("buffer-list", "Buffer List",
"A list with the fragment's buffers", GST_TYPE_FRAGMENT,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class, PROP_CAPS,
g_param_spec_boxed ("caps", "Fragment caps",
"The caps of the fragment's buffer. (NULL = detect)", GST_TYPE_CAPS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
@ -159,12 +115,8 @@ gst_fragment_init (GstFragment * fragment)
fragment->priv = priv = GST_FRAGMENT_GET_PRIVATE (fragment);
g_mutex_init (&fragment->priv->lock);
priv->buffer_list = gst_buffer_list_new ();
priv->size = 0;
priv->buffer_iterator = gst_buffer_list_iterate (priv->buffer_list);
gst_buffer_list_iterator_add_group (priv->buffer_iterator);
fragment->download_start_time = g_get_real_time ();
priv->adapter = gst_adapter_new ();
fragment->download_start_time = gst_util_get_timestamp ();
fragment->start_time = 0;
fragment->stop_time = 0;
fragment->index = 0;
@ -185,7 +137,6 @@ gst_fragment_finalize (GObject * gobject)
GstFragment *fragment = GST_FRAGMENT (gobject);
g_free (fragment->name);
g_mutex_clear (&fragment->priv->lock);
G_OBJECT_CLASS (gst_fragment_parent_class)->finalize (gobject);
}
@ -195,74 +146,34 @@ gst_fragment_dispose (GObject * object)
{
GstFragmentPrivate *priv = GST_FRAGMENT (object)->priv;
if (priv->buffer_list != NULL) {
gst_buffer_list_iterator_free (priv->buffer_iterator);
gst_buffer_list_unref (priv->buffer_list);
priv->buffer_list = NULL;
priv->size = 0;
if (priv->adapter) {
gst_object_unref (priv->adapter);
priv->adapter = NULL;
}
if (priv->caps != NULL) {
gst_caps_unref (priv->caps);
priv->caps = NULL;
if (priv->buffer) {
gst_buffer_unref (priv->buffer);
priv->buffer = NULL;
}
G_OBJECT_CLASS (gst_fragment_parent_class)->dispose (object);
}
GstBufferList *
gst_fragment_get_buffer_list (GstFragment * fragment)
GstBuffer *
gst_fragment_get_buffer (GstFragment * fragment)
{
g_return_val_if_fail (fragment != NULL, NULL);
if (!fragment->completed)
return NULL;
gst_buffer_list_ref (fragment->priv->buffer_list);
return fragment->priv->buffer_list;
}
void
gst_fragment_set_caps (GstFragment * fragment, GstCaps * caps)
{
g_return_if_fail (fragment != NULL);
g_mutex_lock (&fragment->priv->lock);
gst_caps_replace (&fragment->priv->caps, caps);
g_mutex_unlock (&fragment->priv->lock);
}
GstCaps *
gst_fragment_get_caps (GstFragment * fragment)
{
g_return_val_if_fail (fragment != NULL, NULL);
if (!fragment->completed)
return NULL;
g_mutex_lock (&fragment->priv->lock);
if (fragment->priv->caps == NULL) {
GstBuffer *buf = gst_buffer_list_get (fragment->priv->buffer_list, 0, 0);
fragment->priv->caps = gst_type_find_helper_for_buffer (NULL, buf, NULL);
if (!fragment->priv->buffer) {
fragment->priv->buffer = gst_adapter_take_buffer (fragment->priv->adapter,
gst_adapter_available (fragment->priv->adapter));
}
gst_caps_ref (fragment->priv->caps);
g_mutex_unlock (&fragment->priv->lock);
return fragment->priv->caps;
return gst_buffer_ref (fragment->priv->buffer);
}
guint64
gst_fragment_get_buffer_size (GstFragment * fragment)
{
g_return_val_if_fail (fragment != NULL, 0);
if (!fragment->completed)
return 0;
return fragment->priv->size;
}
gboolean
gst_fragment_add_buffer (GstFragment * fragment, GstBuffer * buffer)
{
@ -274,8 +185,20 @@ gst_fragment_add_buffer (GstFragment * fragment, GstBuffer * buffer)
return FALSE;
}
gst_buffer_list_iterator_add (fragment->priv->buffer_iterator, buffer);
fragment->priv->size = fragment->priv->size + GST_BUFFER_SIZE (buffer);
GST_DEBUG ("Adding new buffer to the fragment");
/* We steal the buffers you pass in */
gst_adapter_push (fragment->priv->adapter, buffer);
return TRUE;
}
gsize
gst_fragment_get_total_size (GstFragment * fragment)
{
g_return_val_if_fail (GST_IS_FRAGMENT (fragment), 0);
if (fragment->priv->buffer)
return GST_BUFFER_SIZE (fragment->priv->buffer);
return gst_adapter_available (fragment->priv->adapter);
}

View file

@ -60,11 +60,10 @@ struct _GstFragmentClass
GType gst_fragment_get_type (void);
guint64 gst_fragment_get_buffer_size (GstFragment * fragment);
GstBufferList * gst_fragment_get_buffer_list (GstFragment *fragment);
void gst_fragment_set_caps (GstFragment * fragment, GstCaps * caps);
GstCaps * gst_fragment_get_caps (GstFragment * fragment);
GstBuffer * gst_fragment_get_buffer (GstFragment *fragment);
gboolean gst_fragment_set_headers (GstFragment *fragment, GstBuffer **buffer, guint count);
gboolean gst_fragment_add_buffer (GstFragment *fragment, GstBuffer *buffer);
gsize gst_fragment_get_total_size (GstFragment * fragment);
GstFragment * gst_fragment_new (void);
G_END_DECLS

View file

@ -3256,7 +3256,7 @@ gst_mpd_client_get_next_fragment_duration (GstMpdClient * client)
GstActiveStream *stream;
GstMediaSegment *media_segment;
GST_WARNING ("Stream index: %i", client->stream_idx);
GST_DEBUG ("Stream index: %i", client->stream_idx);
stream = g_list_nth_data (client->active_streams, client->stream_idx);
g_return_val_if_fail (stream != NULL, 0);