gstdashdemux.c 46.9 KB
Newer Older
1
2
3
4
5
6
/*
 * DASH demux plugin for GStreamer
 *
 * gstdashdemux.c
 *
 * Copyright (C) 2012 Orange
7
 *
8
9
10
11
 * Authors:
 *   David Corvoysier <david.corvoysier@orange.com>
 *   Hamid Zakari <hamid.zakari@gmail.com>
 *
12
13
14
 * Copyright (C) 2013 Smart TV Alliance
 *  Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd.
 *
15
16
17
18
19
20
21
22
23
24
25
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
26
 * License along with this library (COPYING); if not, write to the
27
28
29
30
31
32
33
34
35
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
/**
 * SECTION:element-dashdemux
 *
 * DASH demuxer element.
 * <title>Example launch line</title>
 * |[
36
 * gst-launch-1.0 playbin uri="http://www-itec.uni-klu.ac.at/ftp/datasets/mmsys12/RedBullPlayStreets/redbull_4s/RedBullPlayStreets_4s_isoffmain_DIS_23009_1_v_2_1c2_2011_08_30.mpd"
37
38
39
 * ]|
 */

40
/* Implementation notes:
41
 *
42
 * The following section describes how dashdemux works internally.
43
 *
44
 * Introduction:
45
 *
46
47
48
 * dashdemux is a "fake" demux, as unlike traditional demux elements, it
 * doesn't split data streams contained in an enveloppe to expose them
 * to downstream decoding elements.
49
 *
50
51
 * Instead, it parses an XML file called a manifest to identify a set of
 * individual stream fragments it needs to fetch and expose to the actual
52
 * demux elements that will handle them (this behavior is sometimes
53
 * referred as the "demux after a demux" scenario).
54
 *
55
56
 * For a given section of content, several representations corresponding
 * to different bitrates may be available: dashdemux will select the most
57
 * appropriate representation based on local conditions (typically the
58
 * available bandwidth and the amount of buffering available, capped by
59
60
 * a maximum allowed bitrate).
 *
61
62
 * The representation selection algorithm can be configured using
 * specific properties: max bitrate, min/max buffering, bandwidth ratio.
63
64
 *
 *
65
 * General Design:
66
67
 *
 * dashdemux has a single sink pad that accepts the data corresponding
68
 * to the manifest, typically fetched from an HTTP or file source.
69
 *
70
71
72
 * dashdemux exposes the streams it recreates based on the fragments it
 * fetches through dedicated src pads corresponding to the caps of the
 * fragments container (ISOBMFF/MP4 or MPEG2TS).
73
 *
74
75
 * During playback, new representations will typically be exposed as a
 * new set of pads (see 'Switching between representations' below).
76
 *
77
78
79
 * Fragments downloading is performed using a dedicated task that fills
 * an internal queue. Another task is in charge of popping fragments
 * from the queue and pushing them downstream.
80
 *
81
 * Switching between representations:
82
83
 *
 * Decodebin supports scenarios allowing to seamlessly switch from one
84
 * stream to another inside the same "decoding chain".
85
 *
86
87
88
89
 * To achieve that, it combines the elements it autoplugged in chains
 *  and groups, allowing only one decoding group to be active at a given
 * time for a given chain.
 *
90
 * A chain can signal decodebin that it is complete by sending a
91
92
93
94
95
96
97
98
 * no-more-pads event, but even after that new pads can be added to
 * create new subgroups, providing that a new no-more-pads event is sent.
 *
 * We take advantage of that to dynamically create a new decoding group
 * in order to select a different representation during playback.
 *
 * Typically, assuming that each fragment contains both audio and video,
 * the following tree would be created:
99
 *
100
 * chain "DASH Demux"
101
 * |_ group "Representation set 1"
102
103
104
105
 * |   |_ chain "Qt Demux 0"
 * |       |_ group "Stream 0"
 * |           |_ chain "H264"
 * |           |_ chain "AAC"
106
 * |_ group "Representation set 2"
107
108
109
110
111
112
113
114
 *     |_ chain "Qt Demux 1"
 *         |_ group "Stream 1"
 *             |_ chain "H264"
 *             |_ chain "AAC"
 *
 * Or, if audio and video are contained in separate fragments:
 *
 * chain "DASH Demux"
115
 * |_ group "Representation set 1"
116
117
118
119
120
 * |   |_ chain "Qt Demux 0"
 * |   |   |_ group "Stream 0"
 * |   |       |_ chain "H264"
 * |   |_ chain "Qt Demux 1"
 * |       |_ group "Stream 1"
121
 * |           |_ chain "AAC"
122
 * |_ group "Representation set 2"
123
124
125
126
127
 *     |_ chain "Qt Demux 3"
 *     |   |_ group "Stream 2"
 *     |       |_ chain "H264"
 *     |_ chain "Qt Demux 4"
 *         |_ group "Stream 3"
128
 *             |_ chain "AAC"
129
 *
130
131
 * In both cases, when switching from Set 1 to Set 2 an EOS is sent on
 * each end pad corresponding to Rep 0, triggering the "drain" state to
132
 * propagate upstream.
133
134
 * Once both EOS have been processed, the "Set 1" group is completely
 * drained, and decodebin2 will switch to the "Set 2" group.
135
136
137
 *
 * Note: nothing can be pushed to the new decoding group before the
 * old one has been drained, which means that in order to be able to
138
139
 * adapt quickly to bandwidth changes, we will not be able to rely
 * on downstream buffering, and will instead manage an internal queue.
140
 *
141
142
 */

143
144
145
146
147
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <string.h>
148
#include <inttypes.h>
149
#include <gst/base/gsttypefindhelper.h>
150
#include <gst/tag/tag.h>
151
#include "gst/gst-i18n-plugin.h"
152
#include "gstdashdemux.h"
153
#include "gstdash_debug.h"
154

155
156
157
158
159
160
161
162
static GstStaticPadTemplate gst_dash_demux_videosrc_template =
GST_STATIC_PAD_TEMPLATE ("video_%02u",
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate gst_dash_demux_audiosrc_template =
GST_STATIC_PAD_TEMPLATE ("audio_%02u",
163
164
165
166
167
168
169
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
170
    GST_STATIC_CAPS ("application/dash+xml"));
171

172
GST_DEBUG_CATEGORY (gst_dash_demux_debug);
173
174
175
176
177
178
179
180
181
182
183
184
185
#define GST_CAT_DEFAULT gst_dash_demux_debug

enum
{
  PROP_0,

  PROP_MAX_BUFFERING_TIME,
  PROP_BANDWIDTH_USAGE,
  PROP_MAX_BITRATE,
  PROP_LAST
};

/* Default values for properties */
David Corvoysier's avatar
David Corvoysier committed
186
187
188
#define DEFAULT_MAX_BUFFERING_TIME       30     /* in seconds */
#define DEFAULT_BANDWIDTH_USAGE         0.8     /* 0 to 1     */
#define DEFAULT_MAX_BITRATE        24000000     /* in bit/s  */
189
190
191
192
193
194
195
196

/* GObject */
static void gst_dash_demux_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_dash_demux_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static void gst_dash_demux_dispose (GObject * obj);

197
198
199
200
201
202
203
204
205
206
207
/* GstAdaptiveDemux */
static GstClockTime gst_dash_demux_get_duration (GstAdaptiveDemux * ademux);
static gboolean gst_dash_demux_is_live (GstAdaptiveDemux * ademux);
static void gst_dash_demux_reset (GstAdaptiveDemux * ademux);
static gboolean gst_dash_demux_process_manifest (GstAdaptiveDemux * ademux,
    GstBuffer * buf);
static gboolean gst_dash_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek);
static GstFlowReturn
gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream);
static GstFlowReturn gst_dash_demux_stream_seek (GstAdaptiveDemuxStream *
    stream, GstClockTime ts);
208
209
static gboolean
gst_dash_demux_stream_has_next_fragment (GstAdaptiveDemuxStream * stream);
210
211
static GstFlowReturn
gst_dash_demux_stream_advance_fragment (GstAdaptiveDemuxStream * stream);
212
static gboolean
213
gst_dash_demux_stream_advance_subfragment (GstAdaptiveDemuxStream * stream);
214
215
216
217
218
static gboolean gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream *
    stream, guint64 bitrate);
static gint64
gst_dash_demux_get_manifest_update_interval (GstAdaptiveDemux * demux);
static GstFlowReturn
219
gst_dash_demux_update_manifest_data (GstAdaptiveDemux * demux, GstBuffer * buf);
220
221
222
223
224
static gint64
gst_dash_demux_stream_get_fragment_waiting_time (GstAdaptiveDemuxStream *
    stream);
static void gst_dash_demux_advance_period (GstAdaptiveDemux * demux);
static gboolean gst_dash_demux_has_next_period (GstAdaptiveDemux * demux);
225
226
227
228
229
static GstFlowReturn gst_dash_demux_data_received (GstAdaptiveDemux * demux,
    GstAdaptiveDemuxStream * stream);
static GstFlowReturn
gst_dash_demux_stream_fragment_finished (GstAdaptiveDemux * demux,
    GstAdaptiveDemuxStream * stream);
230

231
/* GstDashDemux */
David Corvoysier's avatar
David Corvoysier committed
232
static gboolean gst_dash_demux_setup_all_streams (GstDashDemux * demux);
233
static void gst_dash_demux_stream_free (GstAdaptiveDemuxStream * stream);
234

235
236
static GstCaps *gst_dash_demux_get_input_caps (GstDashDemux * demux,
    GstActiveStream * stream);
237
238
static GstPad *gst_dash_demux_create_pad (GstDashDemux * demux,
    GstActiveStream * stream);
239

240
241
242
243
#define SIDX(s) (&(s)->sidx_parser.sidx)
#define SIDX_ENTRY(s,i) (&(SIDX(s)->entries[(i)]))
#define SIDX_CURRENT_ENTRY(s) SIDX_ENTRY(s, SIDX(s)->entry_index)

Thiago Santos's avatar
Thiago Santos committed
244
#define gst_dash_demux_parent_class parent_class
245
G_DEFINE_TYPE_WITH_CODE (GstDashDemux, gst_dash_demux, GST_TYPE_ADAPTIVE_DEMUX,
Thiago Santos's avatar
Thiago Santos committed
246
    GST_DEBUG_CATEGORY_INIT (gst_dash_demux_debug, "dashdemux", 0,
247
        "dashdemux element")
248
    );
249
250
251
252
253
254

static void
gst_dash_demux_dispose (GObject * obj)
{
  GstDashDemux *demux = GST_DASH_DEMUX (obj);

255
  gst_dash_demux_reset (GST_ADAPTIVE_DEMUX_CAST (demux));
256

257
258
259
  if (demux->client) {
    gst_mpd_client_free (demux->client);
    demux->client = NULL;
260
261
  }

262
  g_mutex_clear (&demux->client_lock);
Thiago Santos's avatar
Thiago Santos committed
263

264
265
266
  G_OBJECT_CLASS (parent_class)->dispose (obj);
}

267
268
269
270
271
272
273
static gboolean
gst_dash_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start,
    gint64 * stop)
{
  GstDashDemux *self = GST_DASH_DEMUX (demux);
  GDateTime *now = g_date_time_new_now_utc ();
  GDateTime *mstart =
274
275
      gst_date_time_to_g_date_time (self->client->
      mpd_node->availabilityStartTime);
276
277
278
279
280
281
282
283
284
285
286
  GTimeSpan stream_now;

  stream_now = g_date_time_difference (now, mstart);
  g_date_time_unref (now);
  g_date_time_unref (mstart);
  *stop = stream_now * GST_USECOND;

  *start = *stop - (self->client->mpd_node->timeShiftBufferDepth * GST_MSECOND);
  return TRUE;
}

287
288
289
290
291
292
293
294
295
296
297
static GstClockTime
gst_dash_demux_get_presentation_offset (GstAdaptiveDemux * demux,
    GstAdaptiveDemuxStream * stream)
{
  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux);

  return gst_mpd_parser_get_stream_presentation_offset (dashdemux->client,
      dashstream->index);
}

298
299
300
301
302
static void
gst_dash_demux_class_init (GstDashDemuxClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
303
  GstAdaptiveDemuxClass *gstadaptivedemux_class;
304
305
306

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
307
  gstadaptivedemux_class = (GstAdaptiveDemuxClass *) klass;
308
309
310
311
312

  gobject_class->set_property = gst_dash_demux_set_property;
  gobject_class->get_property = gst_dash_demux_get_property;
  gobject_class->dispose = gst_dash_demux_dispose;

313
#ifndef GST_REMOVE_DEPRECATED
314
315
  g_object_class_install_property (gobject_class, PROP_MAX_BUFFERING_TIME,
      g_param_spec_uint ("max-buffering-time", "Maximum buffering time",
316
317
          "Maximum number of seconds of buffer accumulated during playback"
          "(deprecated)",
318
          2, G_MAXUINT, DEFAULT_MAX_BUFFERING_TIME,
319
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED));
320
321
322
323

  g_object_class_install_property (gobject_class, PROP_BANDWIDTH_USAGE,
      g_param_spec_float ("bandwidth-usage",
          "Bandwidth usage [0..1]",
324
325
          "Percentage of the available bandwidth to use when "
          "selecting representations (deprecated)",
326
327
          0, 1, DEFAULT_BANDWIDTH_USAGE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
328
#endif
329
330
331
332
333
334
335

  g_object_class_install_property (gobject_class, PROP_MAX_BITRATE,
      g_param_spec_uint ("max-bitrate", "Max bitrate",
          "Max of bitrate supported by target decoder",
          1000, G_MAXUINT, DEFAULT_MAX_BITRATE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

Thiago Santos's avatar
Thiago Santos committed
336
  gst_element_class_add_pad_template (gstelement_class,
337
338
339
      gst_static_pad_template_get (&gst_dash_demux_audiosrc_template));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_dash_demux_videosrc_template));
Thiago Santos's avatar
Thiago Santos committed
340
341
342
343
344
345

  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&sinktemplate));

  gst_element_class_set_static_metadata (gstelement_class,
      "DASH Demuxer",
346
      "Codec/Demuxer/Adaptive",
Thiago Santos's avatar
Thiago Santos committed
347
348
349
350
      "Dynamic Adaptive Streaming over HTTP demuxer",
      "David Corvoysier <david.corvoysier@orange.com>\n\
                Hamid Zakari <hamid.zakari@gmail.com>\n\
                Gianluca Gennari <gennarone@gmail.com>");
351

352
353
354
355
356
357
358

  gstadaptivedemux_class->get_duration = gst_dash_demux_get_duration;
  gstadaptivedemux_class->is_live = gst_dash_demux_is_live;
  gstadaptivedemux_class->reset = gst_dash_demux_reset;
  gstadaptivedemux_class->seek = gst_dash_demux_seek;

  gstadaptivedemux_class->process_manifest = gst_dash_demux_process_manifest;
359
360
  gstadaptivedemux_class->update_manifest_data =
      gst_dash_demux_update_manifest_data;
361
362
363
364
365
  gstadaptivedemux_class->get_manifest_update_interval =
      gst_dash_demux_get_manifest_update_interval;

  gstadaptivedemux_class->has_next_period = gst_dash_demux_has_next_period;
  gstadaptivedemux_class->advance_period = gst_dash_demux_advance_period;
366
367
  gstadaptivedemux_class->stream_has_next_fragment =
      gst_dash_demux_stream_has_next_fragment;
368
369
370
371
372
373
374
375
376
  gstadaptivedemux_class->stream_advance_fragment =
      gst_dash_demux_stream_advance_fragment;
  gstadaptivedemux_class->stream_get_fragment_waiting_time =
      gst_dash_demux_stream_get_fragment_waiting_time;
  gstadaptivedemux_class->stream_seek = gst_dash_demux_stream_seek;
  gstadaptivedemux_class->stream_select_bitrate =
      gst_dash_demux_stream_select_bitrate;
  gstadaptivedemux_class->stream_update_fragment_info =
      gst_dash_demux_stream_update_fragment_info;
377
  gstadaptivedemux_class->stream_free = gst_dash_demux_stream_free;
378
379
  gstadaptivedemux_class->get_live_seek_range =
      gst_dash_demux_get_live_seek_range;
380
381
  gstadaptivedemux_class->get_presentation_offset =
      gst_dash_demux_get_presentation_offset;
382
383
384
}

static void
Thiago Santos's avatar
Thiago Santos committed
385
gst_dash_demux_init (GstDashDemux * demux)
386
387
388
389
390
{
  /* Properties */
  demux->max_buffering_time = DEFAULT_MAX_BUFFERING_TIME * GST_SECOND;
  demux->max_bitrate = DEFAULT_MAX_BITRATE;

391
  g_mutex_init (&demux->client_lock);
392
393
394

  gst_adaptive_demux_set_stream_struct_size (GST_ADAPTIVE_DEMUX_CAST (demux),
      sizeof (GstDashDemuxStream));
395
396
397
398
399
400
}

static void
gst_dash_demux_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
401
  GstAdaptiveDemux *adaptivedemux = GST_ADAPTIVE_DEMUX_CAST (object);
402
403
404
405
406
407
408
  GstDashDemux *demux = GST_DASH_DEMUX (object);

  switch (prop_id) {
    case PROP_MAX_BUFFERING_TIME:
      demux->max_buffering_time = g_value_get_uint (value) * GST_SECOND;
      break;
    case PROP_BANDWIDTH_USAGE:
409
      adaptivedemux->bitrate_limit = g_value_get_float (value);
410
411
412
413
414
415
416
417
418
419
420
421
422
423
      break;
    case PROP_MAX_BITRATE:
      demux->max_bitrate = g_value_get_uint (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_dash_demux_get_property (GObject * object, guint prop_id, GValue * value,
    GParamSpec * pspec)
{
424
  GstAdaptiveDemux *adaptivedemux = GST_ADAPTIVE_DEMUX_CAST (object);
425
426
427
428
  GstDashDemux *demux = GST_DASH_DEMUX (object);

  switch (prop_id) {
    case PROP_MAX_BUFFERING_TIME:
429
      g_value_set_uint (value, demux->max_buffering_time / GST_SECOND);
430
431
      break;
    case PROP_BANDWIDTH_USAGE:
432
      g_value_set_float (value, adaptivedemux->bitrate_limit);
433
434
435
436
437
438
439
440
441
442
      break;
    case PROP_MAX_BITRATE:
      g_value_set_uint (value, demux->max_bitrate);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

443
static gboolean
444
445
gst_dash_demux_setup_mpdparser_streams (GstDashDemux * demux,
    GstMpdClient * client)
446
{
447
  gboolean has_streams = FALSE;
Thiago Santos's avatar
Thiago Santos committed
448
  GList *adapt_sets, *iter;
449

Thiago Santos's avatar
Thiago Santos committed
450
451
452
  adapt_sets = gst_mpd_client_get_adaptation_sets (client);
  for (iter = adapt_sets; iter; iter = g_list_next (iter)) {
    GstAdaptationSetNode *adapt_set_node = iter->data;
453

Thiago Santos's avatar
Thiago Santos committed
454
455
    gst_mpd_client_setup_streaming (client, adapt_set_node);
    has_streams = TRUE;
456
  }
Thiago Santos's avatar
Thiago Santos committed
457

458
459
  if (!has_streams) {
    GST_ELEMENT_ERROR (demux, STREAM, DEMUX, ("Manifest has no playable "
Thiago Santos's avatar
Thiago Santos committed
460
            "streams"), ("No streams could be activated from the manifest"));
461
  }
462
  return has_streams;
463
464
465
466
467
468
469
}

static gboolean
gst_dash_demux_setup_all_streams (GstDashDemux * demux)
{
  guint i;

470
471
472
  GST_DEBUG_OBJECT (demux, "Setting up streams for period %d",
      gst_mpd_client_get_period_index (demux->client));

473
474
  /* clean old active stream list, if any */
  gst_active_streams_free (demux->client);
475

476
477
478
  if (!gst_dash_demux_setup_mpdparser_streams (demux, demux->client)) {
    return FALSE;
  }
479
480
481
482
483
484

  GST_DEBUG_OBJECT (demux, "Creating stream objects");
  for (i = 0; i < gst_mpdparser_get_nb_active_stream (demux->client); i++) {
    GstDashDemuxStream *stream;
    GstActiveStream *active_stream;
    GstCaps *caps;
485
    GstPad *srcpad;
486
    gchar *lang = NULL;
487
    GstTagList *tags = NULL;
488
489
490
491

    active_stream = gst_mpdparser_get_active_stream_by_index (demux->client, i);
    if (active_stream == NULL)
      continue;
492
493
494
    /* TODO: support 'application' mimeType */
    if (active_stream->mimeType == GST_STREAM_APPLICATION)
      continue;
495

496
    srcpad = gst_dash_demux_create_pad (demux, active_stream);
497
498
    caps = gst_dash_demux_get_input_caps (demux, active_stream);
    GST_LOG_OBJECT (demux, "Creating stream %d %" GST_PTR_FORMAT, i, caps);
499
500

    if (active_stream->cur_adapt_set) {
501
502
503
504
505
506
507
508
      GstAdaptationSetNode *adp_set = active_stream->cur_adapt_set;
      lang = adp_set->lang;

      /* Fallback to the language in ContentComponent node */
      if (lang == NULL && g_list_length (adp_set->ContentComponents) == 1) {
        GstContentComponentNode *cc_node = adp_set->ContentComponents->data;
        lang = cc_node->lang;
      }
509
510
511
512
513
514
515
516
    }

    if (lang) {
      if (gst_tag_check_language_code (lang))
        tags = gst_tag_list_new (GST_TAG_LANGUAGE_CODE, lang, NULL);
      else
        tags = gst_tag_list_new (GST_TAG_LANGUAGE_NAME, lang, NULL);
    }
517

518
519
520
521
522
523
524
525
526
    stream = (GstDashDemuxStream *)
        gst_adaptive_demux_stream_new (GST_ADAPTIVE_DEMUX_CAST (demux), srcpad);
    stream->active_stream = active_stream;
    gst_adaptive_demux_stream_set_caps (GST_ADAPTIVE_DEMUX_STREAM_CAST (stream),
        caps);
    if (tags)
      gst_adaptive_demux_stream_set_tags (GST_ADAPTIVE_DEMUX_STREAM_CAST
          (stream), tags);
    stream->index = i;
527
    stream->pending_seek_ts = GST_CLOCK_TIME_NONE;
528
    gst_isoff_sidx_parser_init (&stream->sidx_parser);
529
  }
530
531
532
533

  return TRUE;
}

534
535
static GstClockTime
gst_dash_demux_get_duration (GstAdaptiveDemux * ademux)
536
{
537
  GstDashDemux *demux = GST_DASH_DEMUX_CAST (ademux);
538

539
  g_return_val_if_fail (demux->client != NULL, GST_CLOCK_TIME_NONE);
540

541
542
  return gst_mpd_client_get_media_presentation_duration (demux->client);
}
543

544
545
546
547
static gboolean
gst_dash_demux_is_live (GstAdaptiveDemux * ademux)
{
  GstDashDemux *demux = GST_DASH_DEMUX_CAST (ademux);
548

549
  g_return_val_if_fail (demux->client != NULL, FALSE);
550

551
  return gst_mpd_client_is_live (demux->client);
552
553
554
}

static gboolean
555
gst_dash_demux_setup_streams (GstAdaptiveDemux * demux)
556
{
557
558
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux);
  gboolean ret = TRUE;
559
560
  GstDateTime *now = NULL;
  guint period_idx;
561

562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
  /* setup video, audio and subtitle streams, starting from first Period if
   * non-live */
  period_idx = 0;
  if (gst_mpd_client_is_live (dashdemux->client)) {

    /* get period index for period encompassing the current time */
    now = gst_date_time_new_now_utc ();
    if (dashdemux->client->mpd_node->suggestedPresentationDelay != -1) {
      GstDateTime *target = gst_mpd_client_add_time_difference (now,
          dashdemux->client->mpd_node->suggestedPresentationDelay * -1000);
      gst_date_time_unref (now);
      now = target;
    }
    period_idx =
        gst_mpd_client_get_period_index_at_time (dashdemux->client, now);
    if (period_idx == G_MAXUINT) {
#ifndef GST_DISABLE_GST_DEBUG
      gchar *date_str = gst_date_time_to_iso8601_string (now);
      GST_DEBUG_OBJECT (demux, "Unable to find live period active at %s",
          date_str);
      g_free (date_str);
#endif
      ret = FALSE;
      goto done;
    }
  }

  if (!gst_mpd_client_set_period_index (dashdemux->client, period_idx) ||
590
591
592
593
594
595
596
597
      !gst_dash_demux_setup_all_streams (dashdemux)) {
    ret = FALSE;
    goto done;
  }

  /* If stream is live, try to find the segment that
   * is closest to current time */
  if (gst_mpd_client_is_live (dashdemux->client)) {
598
    GDateTime *gnow;
599
600

    GST_DEBUG_OBJECT (demux, "Seeking to current time of day for live stream ");
601

602
603
604
    gnow = gst_date_time_to_g_date_time (now);
    gst_mpd_client_seek_to_time (dashdemux->client, gnow);
    g_date_time_unref (gnow);
605
606
  } else {
    GST_DEBUG_OBJECT (demux, "Seeking to first segment for on-demand stream ");
607

608
    /* start playing from the first segment */
609
    gst_mpd_client_seek_to_first_segment (dashdemux->client);
610
611
  }

612
done:
613
614
  if (now != NULL)
    gst_date_time_unref (now);
615
616
617
  return ret;
}

618
619
static gboolean
gst_dash_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf)
620
{
621
  GstAdaptiveDemuxClass *klass;
622
623
624
625
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux);
  gboolean ret = FALSE;
  gchar *manifest;
  GstMapInfo mapinfo;
626

627
628
629
  if (dashdemux->client)
    gst_mpd_client_free (dashdemux->client);
  dashdemux->client = gst_mpd_client_new ();
630

631
632
  dashdemux->client->mpd_uri = g_strdup (demux->manifest_uri);
  dashdemux->client->mpd_base_uri = g_strdup (demux->manifest_base_uri);
633

634
635
636
  GST_DEBUG_OBJECT (demux, "Fetched MPD file at URI: %s (base: %s)",
      dashdemux->client->mpd_uri,
      GST_STR_NULL (dashdemux->client->mpd_base_uri));
637

638
639
640
  if (gst_buffer_map (buf, &mapinfo, GST_MAP_READ)) {
    manifest = (gchar *) mapinfo.data;
    if (gst_mpd_parse (dashdemux->client, manifest, mapinfo.size)) {
641
642
643
      if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) {
        klass = GST_ADAPTIVE_DEMUX_GET_CLASS (dashdemux);

644
645
        klass->data_received = gst_dash_demux_data_received;
        klass->finish_fragment = gst_dash_demux_stream_fragment_finished;
646
647
      }

648
649
650
651
652
653
654
655
656
657
      if (gst_mpd_client_setup_media_presentation (dashdemux->client)) {
        ret = TRUE;
      } else {
        GST_ELEMENT_ERROR (demux, STREAM, DECODE,
            ("Incompatible manifest file."), (NULL));
      }
    }
    gst_buffer_unmap (buf, &mapinfo);
  } else {
    GST_WARNING_OBJECT (demux, "Failed to map manifest buffer");
658
  }
659

660
661
662
663
  if (ret)
    ret = gst_dash_demux_setup_streams (demux);

  return ret;
664
665
}

666
static GstPad *
667
gst_dash_demux_create_pad (GstDashDemux * demux, GstActiveStream * stream)
668
669
{
  GstPad *pad;
670
  GstPadTemplate *tmpl;
671
  gchar *name;
672

673
674
675
676
677
678
679
680
681
682
683
684
685
  switch (stream->mimeType) {
    case GST_STREAM_AUDIO:
      name = g_strdup_printf ("audio_%02u", demux->n_audio_streams++);
      tmpl = gst_static_pad_template_get (&gst_dash_demux_audiosrc_template);
      break;
    case GST_STREAM_VIDEO:
      name = g_strdup_printf ("video_%02u", demux->n_video_streams++);
      tmpl = gst_static_pad_template_get (&gst_dash_demux_videosrc_template);
      break;
    default:
      g_assert_not_reached ();
      return NULL;
  }
686
687

  /* Create and activate new pads */
688
689
  pad = gst_ghost_pad_new_no_target_from_template (name, tmpl);
  g_free (name);
690
691
  gst_object_unref (tmpl);

692
693
694
695
696
  gst_pad_set_active (pad, TRUE);
  GST_INFO_OBJECT (demux, "Creating srcpad %s:%s", GST_DEBUG_PAD_NAME (pad));
  return pad;
}

697
static void
698
gst_dash_demux_reset (GstAdaptiveDemux * ademux)
699
{
700
  GstDashDemux *demux = GST_DASH_DEMUX_CAST (ademux);
701

702
  GST_DEBUG_OBJECT (demux, "Resetting demux");
703

704
705
  demux->end_of_period = FALSE;
  demux->end_of_manifest = FALSE;
706

707
708
709
  if (demux->client) {
    gst_mpd_client_free (demux->client);
    demux->client = NULL;
710
  }
711
  demux->client = gst_mpd_client_new ();
712
713
714

  demux->n_audio_streams = 0;
  demux->n_video_streams = 0;
715
716
}

717
718
719
static GstCaps *
gst_dash_demux_get_video_input_caps (GstDashDemux * demux,
    GstActiveStream * stream)
720
{
721
722
723
  guint width = 0, height = 0;
  const gchar *mimeType = NULL;
  GstCaps *caps = NULL;
724

725
726
  if (stream == NULL)
    return NULL;
727

728
729
730
731
  /* if bitstreamSwitching is true we dont need to swich pads on resolution change */
  if (!gst_mpd_client_get_bitstream_switching_flag (stream)) {
    width = gst_mpd_client_get_video_stream_width (stream);
    height = gst_mpd_client_get_video_stream_height (stream);
732
  }
733
734
735
  mimeType = gst_mpd_client_get_stream_mimeType (stream);
  if (mimeType == NULL)
    return NULL;
736

737
738
739
740
  caps = gst_caps_from_string (mimeType);
  if (width > 0 && height > 0) {
    gst_caps_set_simple (caps, "width", G_TYPE_INT, width, "height",
        G_TYPE_INT, height, NULL);
741
742
  }

743
  return caps;
744
}
745

746
747
748
static GstCaps *
gst_dash_demux_get_audio_input_caps (GstDashDemux * demux,
    GstActiveStream * stream)
749
{
750
751
752
  guint rate = 0, channels = 0;
  const gchar *mimeType;
  GstCaps *caps = NULL;
753

754
755
  if (stream == NULL)
    return NULL;
756

757
758
759
760
  /* if bitstreamSwitching is true we dont need to swich pads on rate/channels change */
  if (!gst_mpd_client_get_bitstream_switching_flag (stream)) {
    channels = gst_mpd_client_get_audio_stream_num_channels (stream);
    rate = gst_mpd_client_get_audio_stream_rate (stream);
761
  }
762
763
764
  mimeType = gst_mpd_client_get_stream_mimeType (stream);
  if (mimeType == NULL)
    return NULL;
765

766
767
768
769
770
771
772
  caps = gst_caps_from_string (mimeType);
  if (rate > 0) {
    gst_caps_set_simple (caps, "rate", G_TYPE_INT, rate, NULL);
  }
  if (channels > 0) {
    gst_caps_set_simple (caps, "channels", G_TYPE_INT, channels, NULL);
  }
773

774
  return caps;
775
776
}

777
778
779
780
781
782
static GstCaps *
gst_dash_demux_get_application_input_caps (GstDashDemux * demux,
    GstActiveStream * stream)
{
  const gchar *mimeType;
  GstCaps *caps = NULL;
Thiago Santos's avatar
Thiago Santos committed
783

784
785
  if (stream == NULL)
    return NULL;
786

787
788
789
  mimeType = gst_mpd_client_get_stream_mimeType (stream);
  if (mimeType == NULL)
    return NULL;
Thiago Santos's avatar
Thiago Santos committed
790

791
  caps = gst_caps_from_string (mimeType);
792

793
  return caps;
794
795
}

796
797
static GstCaps *
gst_dash_demux_get_input_caps (GstDashDemux * demux, GstActiveStream * stream)
Thiago Santos's avatar
Thiago Santos committed
798
{
799
800
801
802
803
804
805
806
807
  switch (stream->mimeType) {
    case GST_STREAM_VIDEO:
      return gst_dash_demux_get_video_input_caps (demux, stream);
    case GST_STREAM_AUDIO:
      return gst_dash_demux_get_audio_input_caps (demux, stream);
    case GST_STREAM_APPLICATION:
      return gst_dash_demux_get_application_input_caps (demux, stream);
    default:
      return GST_CAPS_NONE;
Thiago Santos's avatar
Thiago Santos committed
808
809
810
  }
}

811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
static void
gst_dash_demux_stream_update_headers_info (GstAdaptiveDemuxStream * stream)
{
  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
  gchar *path = NULL;

  gst_mpd_client_get_next_header (dashdemux->client,
      &path, dashstream->index,
      &stream->fragment.header_range_start, &stream->fragment.header_range_end);

  if (path != NULL && strncmp (path, "http://", 7) != 0) {
    stream->fragment.header_uri =
        gst_uri_join_strings (gst_mpdparser_get_baseURL (dashdemux->client,
            dashstream->index), path);
    g_free (path);
  } else {
    stream->fragment.header_uri = path;
  }
  path = NULL;

  gst_mpd_client_get_next_header_index (dashdemux->client,
      &path, dashstream->index,
      &stream->fragment.index_range_start, &stream->fragment.index_range_end);

  if (path != NULL && strncmp (path, "http://", 7) != 0) {
    stream->fragment.index_uri =
        gst_uri_join_strings (gst_mpdparser_get_baseURL (dashdemux->client,
            dashstream->index), path);
    g_free (path);
  } else {
    stream->fragment.index_uri = path;
  }
}

846
static GstFlowReturn
847
gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
848
{
849
850
851
852
  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
  GstClockTime ts;
  GstMediaFragmentInfo fragment;
853
  gboolean isombff;
854
855
856

  gst_adaptive_demux_stream_fragment_clear (&stream->fragment);

857
858
859
860
861
862
863
864
865
866
867
868
  isombff = gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client);

  if (GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER (stream) && isombff) {
    gst_dash_demux_stream_update_headers_info (stream);
    dashstream->sidx_base_offset = stream->fragment.index_range_end + 1;
    if (dashstream->sidx_index != 0) {
      /* request only the index to be downloaded as we need to reposition the
       * stream to a subsegment */
      return GST_FLOW_OK;
    }
  }

869
870
871
  if (gst_mpd_client_get_next_fragment_timestamp (dashdemux->client,
          dashstream->index, &ts)) {
    if (GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER (stream)) {
872
      gst_dash_demux_stream_update_headers_info (stream);
873
    }
874

875
876
    gst_mpd_client_get_next_fragment (dashdemux->client, dashstream->index,
        &fragment);
David Corvoysier's avatar
David Corvoysier committed
877

878
    stream->fragment.uri = fragment.uri;
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
    if (isombff && dashstream->sidx_index != 0) {
      GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream);
      stream->fragment.range_start =
          dashstream->sidx_base_offset + entry->offset;
      stream->fragment.timestamp = entry->pts;
      stream->fragment.duration = entry->duration;
      if (stream->demux->segment.rate < 0.0) {
        stream->fragment.range_end =
            stream->fragment.range_start + entry->size - 1;
      } else {
        stream->fragment.range_end = fragment.range_end;
      }
    } else {
      stream->fragment.timestamp = fragment.timestamp;
      stream->fragment.duration = fragment.duration;
      stream->fragment.range_start =
          MAX (fragment.range_start, dashstream->sidx_base_offset);
      stream->fragment.range_end = fragment.range_end;
    }
898

899
    return GST_FLOW_OK;
900
901
  }

902
903
  return GST_FLOW_EOS;
}
David Corvoysier's avatar
David Corvoysier committed
904

905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
static void
gst_dash_demux_stream_sidx_seek (GstDashDemuxStream * dashstream,
    GstClockTime ts)
{
  GstSidxBox *sidx = SIDX (dashstream);
  gint i;

  /* TODO optimize to a binary search */
  for (i = 0; i < sidx->entries_count; i++) {
    if (sidx->entries[i].pts + sidx->entries[i].duration >= ts)
      break;
  }
  sidx->entry_index = i;
  dashstream->sidx_index = i;
  if (i < sidx->entries_count)
    dashstream->sidx_current_remaining = sidx->entries[i].size;
  else
    dashstream->sidx_current_remaining = 0;
}

925
926
927
928
929
static GstFlowReturn
gst_dash_demux_stream_seek (GstAdaptiveDemuxStream * stream, GstClockTime ts)
{
  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
David Corvoysier's avatar
David Corvoysier committed
930

931
932
933
934
935
936
937
938
939
  if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) {
    if (dashstream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
      gst_dash_demux_stream_sidx_seek (dashstream, ts);
    } else {
      /* no index yet, seek when we have it */
      dashstream->pending_seek_ts = ts;
    }
  }

940
941
  gst_mpd_client_stream_seek (dashdemux->client, dashstream->active_stream, ts);
  return GST_FLOW_OK;
942
943
}

944
static gboolean
945
946
947
948
949
950
951
gst_dash_demux_stream_advance_subfragment (GstAdaptiveDemuxStream * stream)
{
  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;

  GstSidxBox *sidx = SIDX (dashstream);
  gboolean fragment_finished = TRUE;

952
953
954
955
956
957
958
959
960
961
962
  if (dashstream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
    if (stream->demux->segment.rate > 0.0) {
      sidx->entry_index++;
      if (sidx->entry_index < sidx->entries_count) {
        fragment_finished = FALSE;
      }
    } else {
      sidx->entry_index--;
      if (sidx->entry_index >= 0) {
        fragment_finished = FALSE;
      }
963
964
965
    }
  }

966
967
968
969
  GST_DEBUG_OBJECT (stream->pad, "New sidx index: %d / %d. "
      "Finished fragment: %d", sidx->entry_index, sidx->entries_count,
      fragment_finished);

970
971
972
  if (!fragment_finished) {
    dashstream->sidx_current_remaining = sidx->entries[sidx->entry_index].size;
  }
973
  return !fragment_finished;
974
975
}

976
977
978
979
980
981
982
983
984
985
static gboolean
gst_dash_demux_stream_has_next_fragment (GstAdaptiveDemuxStream * stream)
{
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;

  return gst_mpd_client_has_next_segment (dashdemux->client,
      dashstream->active_stream, stream->demux->segment.rate > 0.0);
}

986
987
static GstFlowReturn
gst_dash_demux_stream_advance_fragment (GstAdaptiveDemuxStream * stream)
988
{
989
990
  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
991

992
993
  GST_DEBUG_OBJECT (stream->pad, "Advance fragment");

994
  if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) {
995
996
    if (gst_dash_demux_stream_advance_subfragment (stream))
      return GST_FLOW_OK;
997
998
  }

999
1000
  return gst_mpd_client_advance_segment (dashdemux->client,
      dashstream->active_stream, stream->demux->segment.rate > 0.0);
1001
1002
}

1003
1004
1005
static gboolean
gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream,
    guint64 bitrate)
1006
{
1007
  GstActiveStream *active_stream = NULL;
1008
  GList *rep_list = NULL;
1009
  gint new_index;
1010
1011
1012
  GstDashDemux *demux = GST_DASH_DEMUX_CAST (stream->demux);
  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
  gboolean ret = FALSE;
1013

1014
1015
1016
1017
  active_stream = dashstream->active_stream;
  if (active_stream == NULL) {
    goto end;
  }
1018

1019
1020
1021
  /* retrieve representation list */
  if (active_stream->cur_adapt_set)
    rep_list = active_stream->cur_adapt_set->Representations;
1022
1023
1024
  if (!rep_list) {
    goto end;
  }
1025
1026
1027

  GST_DEBUG_OBJECT (stream->pad,
      "Trying to change to bitrate: %" G_GUINT64_FORMAT, bitrate);
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038

  /* get representation index with current max_bandwidth */
  new_index = gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, bitrate);

  /* if no representation has the required bandwidth, take the lowest one */
  if (new_index == -1)
    new_index = gst_mpdparser_get_rep_idx_with_min_bandwidth (rep_list);

  if (new_index != active_stream->representation_idx) {
    GstRepresentationNode *rep = g_list_nth_data (rep_list, new_index);
    GST_INFO_OBJECT (demux, "Changing representation idx: %d %d %u",
1039
        dashstream->index, new_index, rep->bandwidth);
1040
    if (gst_mpd_client_setup_representation (demux->client, active_stream, rep)) {
1041
1042
      GstCaps *caps;

1043
1044
      GST_INFO_OBJECT (demux, "Switching bitrate to %d",
          active_stream->cur_representation->bandwidth);
1045
1046
1047
      caps = gst_dash_demux_get_input_caps (demux, active_stream);
      gst_adaptive_demux_stream_set_caps (stream, caps);
      ret = TRUE;
1048

1049
1050
    } else {
      GST_WARNING_OBJECT (demux, "Can not switch representation, aborting...");
1051
1052
1053
    }
  }

1054
1055
  if (gst_mpd_client_has_isoff_ondemand_profile (demux->client)) {

1056
1057
    /* store our current position to change to the same one in a different
     * representation if needed */
1058
1059
1060
1061
1062
1063
1064
1065
1066
    dashstream->sidx_index = SIDX (dashstream)->entry_index;
    if (ret) {
      /* TODO cache indexes to avoid re-downloading and parsing */
      /* if we switched, we need a new index */
      gst_isoff_sidx_parser_clear (&dashstream->sidx_parser);
      gst_isoff_sidx_parser_init (&dashstream->sidx_parser);
    }
  }

1067
end:
1068
  return ret;
1069
}
1070

1071
1072
static gboolean
gst_dash_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek)
1073
{
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
  gdouble rate;
  GstFormat format;
  GstSeekFlags flags;
  GstSeekType start_type, stop_type;
  gint64 start, stop;
  GList *list;
  GstClockTime current_pos, target_pos;
  guint current_period;
  GstStreamPeriod *period;
  GList *iter;
  GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux);
1085
  gboolean switched_period = FALSE;
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108

  gst_event_parse_seek (seek, &rate, &format, &flags, &start_type, &start,
      &stop_type, &stop);

  /* TODO check if start-type/stop-type is SET */
  if (demux->segment.rate > 0.0)
    target_pos = (GstClockTime) demux->segment.start;
  else
    target_pos = (GstClockTime) demux->segment.stop;

  /* select the requested Period in the Media Presentation */
  current_period = 0;
  for (list = g_list_first (dashdemux->client->periods); list;
      list = g_list_next (list)) {
    period = list->data;
    current_pos = period->start;
    current_period = period->number;
    GST_DEBUG_OBJECT (demux, "Looking at period %u pos %" GST_TIME_FORMAT,
        current_period, GST_TIME_ARGS (current_pos));
    if (current_pos <= target_pos
        && target_pos < current_pos + period->duration) {
      break;
    }
1109
  }
1110
1111
1112
  if (list == NULL) {
    GST_WARNING_OBJECT (demux, "Could not find seeked Period");
    return FALSE;