qtdemux.c 494 KB
Newer Older
Artyom Baginski's avatar
Artyom Baginski committed
1
2
/* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3
 * Copyright (C) <2003> David A. Schleef <ds@schleef.org>
4
 * Copyright (C) <2006> Wim Taymans <wim@fluendo.com>
5
 * Copyright (C) <2007> Julien Moutte <julien@fluendo.com>
6
 * Copyright (C) <2009> Tim-Philipp Müller <tim centricular net>
7
 * Copyright (C) <2009> STEricsson <benjamin.gaignard@stericsson.com>
8
 * Copyright (C) <2013> Sreerenj Balachandran <sreerenj.balachandran@intel.com>
9
10
 * Copyright (C) <2013> Intel Corporation
 * Copyright (C) <2014> Centricular Ltd
11
 * Copyright (C) <2015> YouView TV Ltd.
12
 * Copyright (C) <2016> British Broadcasting Corporation
13
 *
Artyom Baginski's avatar
Artyom Baginski committed
14
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 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
 * License along with this library; if not, write to the
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
26
27
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
Artyom Baginski's avatar
Artyom Baginski committed
28
29
 */

30
31
32
33
/**
 * SECTION:element-qtdemux
 *
 * Demuxes a .mov file into raw or compressed audio and/or video streams.
34
 *
35
36
 * This element supports both push and pull-based scheduling, depending on the
 * capabilities of the upstream elements.
37
38
 *
 * <refsect2>
39
 * <title>Example launch line</title>
40
 * |[
41
 * gst-launch-1.0 filesrc location=test.mov ! qtdemux name=demux  demux.audio_0 ! queue ! decodebin ! audioconvert ! audioresample ! autoaudiosink   demux.video_0 ! queue ! decodebin ! videoconvert ! videoscale ! autovideosink
42
 * ]| Play (parse and decode) a .mov file and try to output it to
43
44
45
46
47
48
 * an automatically detected soundcard and videosink. If the MOV file contains
 * compressed audio or video data, this will only work if you have the
 * right decoder elements/plugins installed.
 * </refsect2>
 */

49
50
51
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
52
53
54

#include "gst/gst-i18n-plugin.h"

55
#include <glib/gprintf.h>
56
#include <gst/tag/tag.h>
Wim Taymans's avatar
Wim Taymans committed
57
#include <gst/audio/audio.h>
58
#include <gst/video/video.h>
59
60
#include <gst/riff/riff.h>
#include <gst/pbutils/pbutils.h>
61

62
#include "qtatomparser.h"
63
64
#include "qtdemux_types.h"
#include "qtdemux_dump.h"
65
#include "fourcc.h"
66
#include "descriptors.h"
67
#include "qtdemux_lang.h"
68
#include "qtdemux.h"
69
#include "qtpalette.h"
70

71
#include <stdio.h>
72
#include <stdlib.h>
Artyom Baginski's avatar
Artyom Baginski committed
73
#include <string.h>
74

75
76
77
#include <math.h>
#include <gst/math-compat.h>

78
79
80
#ifdef HAVE_ZLIB
# include <zlib.h>
#endif
81

82
83
84
/* max. size considered 'sane' for non-mdat atoms */
#define QTDEMUX_MAX_ATOM_SIZE (25*1024*1024)

85
/* if the sample index is larger than this, something is likely wrong */
Michael Olbrich's avatar
Michael Olbrich committed
86
#define QTDEMUX_MAX_SAMPLE_INDEX_SIZE (200*1024*1024)
87

88
89
90
91
92
93
/* For converting qt creation times to unix epoch times */
#define QTDEMUX_SECONDS_PER_DAY (60 * 60 * 24)
#define QTDEMUX_LEAP_YEARS_FROM_1904_TO_1970 17
#define QTDEMUX_SECONDS_FROM_1904_TO_1970 (((1970 - 1904) * (guint64) 365 + \
    QTDEMUX_LEAP_YEARS_FROM_1904_TO_1970) * QTDEMUX_SECONDS_PER_DAY)

94
95
#define QTDEMUX_TREE_NODE_FOURCC(n) (QT_FOURCC(((guint8 *) (n)->data) + 4))

96
#define STREAM_IS_EOS(s) ((s)->time_position == GST_CLOCK_TIME_NONE)
97

98
99
#define ABSDIFF(x, y) ( (x) > (y) ? ((x) - (y)) : ((y) - (x)) )

100
#define QTDEMUX_STREAM(s) ((QtDemuxStream *)(s))
101
102
103
104
105
#define QTDEMUX_N_STREAMS(demux) ((demux)->active_streams->len)
#define QTDEMUX_NTH_STREAM(demux,idx) \
   QTDEMUX_STREAM(g_ptr_array_index((demux)->active_streams,idx))
#define QTDEMUX_NTH_OLD_STREAM(demux,idx) \
   QTDEMUX_STREAM(g_ptr_array_index((demux)->old_streams,idx))
106

107
GST_DEBUG_CATEGORY (qtdemux_debug);
108
#define GST_CAT_DEFAULT qtdemux_debug
109

110
typedef struct _QtDemuxSegment QtDemuxSegment;
111
typedef struct _QtDemuxSample QtDemuxSample;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
112

113
114
typedef struct _QtDemuxCencSampleSetInfo QtDemuxCencSampleSetInfo;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
115
116
struct _QtDemuxSample
{
117
  guint32 size;
118
  gint32 pts_offset;            /* Add this value to timestamp to get the pts */
119
  guint64 offset;
120
121
  guint64 timestamp;            /* DTS In mov time */
  guint32 duration;             /* In mov time */
122
  gboolean keyframe;            /* TRUE when this packet is a keyframe */
123
124
};

125
126
127
128
129
130
131
/* Macros for converting to/from timescale */
#define QTSTREAMTIME_TO_GSTTIME(stream, value) (gst_util_uint64_scale((value), GST_SECOND, (stream)->timescale))
#define GSTTIME_TO_QTSTREAMTIME(stream, value) (gst_util_uint64_scale((value), (stream)->timescale, GST_SECOND))

#define QTTIME_TO_GSTTIME(qtdemux, value) (gst_util_uint64_scale((value), GST_SECOND, (qtdemux)->timescale))
#define GSTTIME_TO_QTTIME(qtdemux, value) (gst_util_uint64_scale((value), (qtdemux)->timescale, GST_SECOND))

132
/* timestamp is the DTS */
133
#define QTSAMPLE_DTS(stream,sample) (QTSTREAMTIME_TO_GSTTIME((stream), (sample)->timestamp))
134
/* timestamp + offset + cslg_shift is the outgoing PTS */
Nicolas Dufresne's avatar
Nicolas Dufresne committed
135
#define QTSAMPLE_PTS(stream,sample) (QTSTREAMTIME_TO_GSTTIME((stream), (sample)->timestamp + (stream)->cslg_shift + (sample)->pts_offset))
136
137
/* timestamp + offset is the PTS used for internal seek calcuations */
#define QTSAMPLE_PTS_NO_CSLG(stream,sample) (QTSTREAMTIME_TO_GSTTIME((stream), (sample)->timestamp + (sample)->pts_offset))
138
/* timestamp + duration - dts is the duration */
139
#define QTSAMPLE_DUR_DTS(stream, sample, dts) (QTSTREAMTIME_TO_GSTTIME ((stream), (sample)->timestamp + (sample)->duration) - (dts))
140
141
142

#define QTSAMPLE_KEYFRAME(stream,sample) ((stream)->all_keyframe || (sample)->keyframe)

143
144
145
146
147
148
149
150
151
152
153
154
#define QTDEMUX_EXPOSE_GET_LOCK(demux) (&((demux)->expose_lock))
#define QTDEMUX_EXPOSE_LOCK(demux) G_STMT_START { \
    GST_TRACE("Locking from thread %p", g_thread_self()); \
    g_mutex_lock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \
    GST_TRACE("Locked from thread %p", g_thread_self()); \
 } G_STMT_END

#define QTDEMUX_EXPOSE_UNLOCK(demux) G_STMT_START { \
    GST_TRACE("Unlocking from thread %p", g_thread_self()); \
    g_mutex_unlock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \
 } G_STMT_END

155
156
157
158
159
160
161
162
163
164
165
/*
 * Quicktime has tracks and segments. A track is a continuous piece of
 * multimedia content. The track is not always played from start to finish but
 * instead, pieces of the track are 'cut out' and played in sequence. This is
 * what the segments do.
 *
 * Inside the track we have keyframes (K) and delta frames. The track has its
 * own timing, which starts from 0 and extends to end. The position in the track
 * is called the media_time.
 *
 * The segments now describe the pieces that should be played from this track
166
 * and are basically tuples of media_time/duration/rate entries. We can have
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
 * multiple segments and they are all played after one another. An example:
 *
 * segment 1: media_time: 1 second, duration: 1 second, rate 1
 * segment 2: media_time: 3 second, duration: 2 second, rate 2
 *
 * To correctly play back this track, one must play: 1 second of media starting
 * from media_time 1 followed by 2 seconds of media starting from media_time 3
 * at a rate of 2.
 *
 * Each of the segments will be played at a specific time, the first segment at
 * time 0, the second one after the duration of the first one, etc.. Note that
 * the time in resulting playback is not identical to the media_time of the
 * track anymore.
 *
 * Visually, assuming the track has 4 second of media_time:
 *
 *                (a)                   (b)          (c)              (d)
 *         .-----------------------------------------------------------.
 * track:  | K.....K.........K........K.......K.......K...........K... |
 *         '-----------------------------------------------------------'
Stefan Kost's avatar
Stefan Kost committed
187
 *         0              1              2              3              4
188
189
190
191
192
193
 *           .------------^              ^   .----------^              ^
 *          /              .-------------'  /       .------------------'
 *         /              /          .-----'       /
 *         .--------------.         .--------------.
 *         | segment 1    |         | segment 2    |
 *         '--------------'         '--------------'
Stefan Kost's avatar
Stefan Kost committed
194
 *
195
 * The challenge here is to cut out the right pieces of the track for each of
196
197
 * the playback segments. This fortunately can easily be done with the SEGMENT
 * events of GStreamer.
198
199
200
201
202
203
204
205
 *
 * For playback of segment 1, we need to provide the decoder with the keyframe
 * (a), in the above figure, but we must instruct it only to output the decoded
 * data between second 1 and 2. We do this with a SEGMENT event for 1 to 2, time
 * position set to the time of the segment: 0.
 *
 * We then proceed to push data from keyframe (a) to frame (b). The decoder
 * decodes but clips all before media_time 1.
Stefan Kost's avatar
Stefan Kost committed
206
 *
207
208
209
210
211
212
 * After finishing a segment, we push out a new SEGMENT event with the clipping
 * boundaries of the new data.
 *
 * This is a good usecase for the GStreamer accumulated SEGMENT events.
 */

213
214
215
struct _QtDemuxSegment
{
  /* global time and duration, all gst time */
216
217
218
  GstClockTime time;
  GstClockTime stop_time;
  GstClockTime duration;
219
  /* media time of trak, all gst time */
220
221
  GstClockTime media_start;
  GstClockTime media_stop;
222
  gdouble rate;
223
224
  /* Media start time in trak timescale units */
  guint32 trak_media_start;
225
226
};

227
228
#define QTSEGMENT_IS_EMPTY(s) ((s)->media_start == GST_CLOCK_TIME_NONE)

229
230
231
232
233
234
235
/* Used with fragmented MP4 files (mfra atom) */
typedef struct
{
  GstClockTime ts;
  guint64 moof_offset;
} QtDemuxRandomAccessEntry;

236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
typedef struct _QtDemuxStreamStsdEntry
{
  GstCaps *caps;
  guint32 fourcc;
  gboolean sparse;

  /* video info */
  gint width;
  gint height;
  gint par_w;
  gint par_h;
  /* Numerator/denominator framerate */
  gint fps_n;
  gint fps_d;
  GstVideoColorimetry colorimetry;
  guint16 bits_per_sample;
  guint16 color_table_id;
  GstMemory *rgb8_palette;
  guint interlace_mode;
  guint field_order;

  /* audio info */
  gdouble rate;
  gint n_channels;
  guint samples_per_packet;
  guint samples_per_frame;
  guint bytes_per_packet;
  guint bytes_per_sample;
  guint bytes_per_frame;
  guint compression;

  /* if we use chunks or samples */
  gboolean sampled;
  guint padding;

} QtDemuxStreamStsdEntry;

#define CUR_STREAM(s) (&((s)->stsd_entries[(s)->cur_stsd_entry_index]))

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
275
276
struct _QtDemuxStream
{
277
278
  GstPad *pad;

279
  GstQTDemux *demux;
280
  gchar *stream_id;
281

282
283
284
285
  QtDemuxStreamStsdEntry *stsd_entries;
  guint stsd_entries_length;
  guint cur_stsd_entry_index;

286
  /* stream type */
287
288
  guint32 subtype;

289
290
291
292
  gboolean new_caps;            /* If TRUE, caps need to be generated (by
                                 * calling _configure_stream()) This happens
                                 * for MSS and fragmented streams */

293
  gboolean new_stream;          /* signals that a stream_start is required */
294
295
296
297
  gboolean on_keyframe;         /* if this stream last pushed buffer was a
                                 * keyframe. This is important to identify
                                 * where to stop pushing buffers after a
                                 * segment stop time */
298

299
300
301
  /* if the stream has a redirect URI in its headers, we store it here */
  gchar *redirect_uri;

302
303
304
  /* track id */
  guint track_id;

305
  /* duration/scale */
306
  guint64 duration;             /* in timescale units */
307
  guint32 timescale;
308

309
  /* language */
310
  gchar lang_id[4];             /* ISO 639-2T language code */
311

312
313
314
315
  /* our samples */
  guint32 n_samples;
  QtDemuxSample *samples;
  gboolean all_keyframe;        /* TRUE when all samples are keyframes (no stss) */
316
317
318
  guint32 n_samples_moof;       /* sample count in a moof */
  guint64 duration_moof;        /* duration in timescale of a moof, used for figure out
                                 * the framerate of fragmented format stream */
319
  guint64 duration_last_moof;
320
321
322
323
324
325
326

  guint32 offset_in_sample;     /* Offset in the current sample, used for
                                 * streams which have got exceedingly big
                                 * sample size (such as 24s of raw audio).
                                 * Only used when max_buffer_size is non-NULL */
  guint32 max_buffer_size;      /* Maximum allowed size for output buffers.
                                 * Currently only set for raw audio streams*/
327

328
  /* video info */
329
330
331
  /* aspect ratio */
  gint display_width;
  gint display_height;
332

333
334
335
336
337
  /* allocation */
  gboolean use_allocator;
  GstAllocator *allocator;
  GstAllocationParams params;

338
339
  gsize alignment;

340
341
  /* when a discontinuity is pending */
  gboolean discont;
342

343
344
345
  /* list of buffers to push first */
  GSList *buffers;

346
347
348
349
  /* if we need to clip this buffer. This is only needed for uncompressed
   * data */
  gboolean need_clip;

350
351
352
  /* buffer needs some custom processing, e.g. subtitles */
  gboolean need_process;

353
354
355
  /* current position */
  guint32 segment_index;
  guint32 sample_index;
356
  GstClockTime time_position;   /* in gst time */
357
  guint64 accumulated_base;
358

359
360
361
  /* the Gst segment we are processing out, used for clipping */
  GstSegment segment;

362
363
364
  /* quicktime segments */
  guint32 n_segments;
  QtDemuxSegment *segments;
365
  gboolean dummy_segment;
366
367
  guint32 from_sample;
  guint32 to_sample;
368
369

  gboolean sent_eos;
370
  GstTagList *stream_tags;
371
  gboolean send_global_tags;
372
373

  GstEvent *pending_event;
374
375
376
377
378
379
380
381
382

  GstByteReader stco;
  GstByteReader stsz;
  GstByteReader stsc;
  GstByteReader stts;
  GstByteReader stss;
  GstByteReader stps;
  GstByteReader ctts;

383
  gboolean chunks_are_samples;  /* TRUE means treat chunks as samples */
384
  gint64 stbl_index;
385
386
  /* stco */
  guint co_size;
387
388
389
390
391
  GstByteReader co_chunk;
  guint32 first_chunk;
  guint32 current_chunk;
  guint32 last_chunk;
  guint32 samples_per_chunk;
392
  guint32 stsd_sample_description_id;
393
  guint32 stco_sample_index;
394
395
396
  /* stsz */
  guint32 sample_size;          /* 0 means variable sizes are stored in stsz */
  /* stsc */
397
  guint32 stsc_index;
398
  guint32 n_samples_per_chunk;
399
400
401
  guint32 stsc_chunk_index;
  guint32 stsc_sample_index;
  guint64 chunk_offset;
402
  /* stts */
403
404
  guint32 stts_index;
  guint32 stts_samples;
405
  guint32 n_sample_times;
406
  guint32 stts_sample_index;
407
  guint64 stts_time;
408
  guint32 stts_duration;
409
  /* stss */
410
  gboolean stss_present;
411
  guint32 n_sample_syncs;
412
  guint32 stss_index;
413
  /* stps */
414
  gboolean stps_present;
415
  guint32 n_sample_partial_syncs;
416
  guint32 stps_index;
417
418
419
  QtDemuxRandomAccessEntry *ra_entries;
  guint n_ra_entries;

420
421
  const QtDemuxRandomAccessEntry *pending_seek;

422
423
424
  /* ctts */
  gboolean ctts_present;
  guint32 n_composition_times;
425
426
427
428
  guint32 ctts_index;
  guint32 ctts_sample_index;
  guint32 ctts_count;
  gint32 ctts_soffset;
429

Nicolas Dufresne's avatar
Nicolas Dufresne committed
430
431
432
  /* cslg */
  guint32 cslg_shift;

433
434
  /* fragmented */
  gboolean parsed_trex;
435
  guint32 def_sample_description_index; /* index is 1-based */
436
437
438
  guint32 def_sample_duration;
  guint32 def_sample_size;
  guint32 def_sample_flags;
439
440

  gboolean disabled;
441
442
443
444

  /* stereoscopic video streams */
  GstVideoMultiviewMode multiview_mode;
  GstVideoMultiviewFlags multiview_flags;
445
446
447
448
449
450
451

  /* protected streams */
  gboolean protected;
  guint32 protection_scheme_type;
  guint32 protection_scheme_version;
  gpointer protection_scheme_info;      /* specific to the protection scheme */
  GQueue protection_scheme_event_queue;
452
453

  gint ref_count;               /* atomic */
454
455
456
457
458
459
460
461
462
463
};

/* Contains properties and cryptographic info for a set of samples from a
 * track protected using Common Encryption (cenc) */
struct _QtDemuxCencSampleSetInfo
{
  GstStructure *default_properties;

  /* @crypto_info holds one GstStructure per sample */
  GPtrArray *crypto_info;
464
465
};

466
467
static const gchar *
qt_demux_state_string (enum QtDemuxState state)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
468
{
469
470
471
472
473
474
475
476
477
478
479
480
481
  switch (state) {
    case QTDEMUX_STATE_INITIAL:
      return "<INITIAL>";
    case QTDEMUX_STATE_HEADER:
      return "<HEADER>";
    case QTDEMUX_STATE_MOVIE:
      return "<MOVIE>";
    case QTDEMUX_STATE_BUFFER_MDAT:
      return "<BUFFER_MDAT>";
    default:
      return "<UNKNOWN>";
  }
}
482

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
483
static GNode *qtdemux_tree_get_child_by_type (GNode * node, guint32 fourcc);
484
static GNode *qtdemux_tree_get_child_by_type_full (GNode * node,
485
    guint32 fourcc, GstByteReader * parser);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
486
static GNode *qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc);
487
488
static GNode *qtdemux_tree_get_sibling_by_type_full (GNode * node,
    guint32 fourcc, GstByteReader * parser);
489

490
491
static GstFlowReturn qtdemux_add_fragmented_samples (GstQTDemux * qtdemux);

492
493
static void gst_qtdemux_check_send_pending_segment (GstQTDemux * demux);

David Schleef's avatar
David Schleef committed
494
static GstStaticPadTemplate gst_qtdemux_sink_template =
495
    GST_STATIC_PAD_TEMPLATE ("sink",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
496
    GST_PAD_SINK,
497
    GST_PAD_ALWAYS,
498
499
    GST_STATIC_CAPS ("video/quicktime; video/mj2; audio/x-m4a; "
        "application/x-3gp")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
500
    );
David Schleef's avatar
David Schleef committed
501
502

static GstStaticPadTemplate gst_qtdemux_videosrc_template =
Wim Taymans's avatar
Wim Taymans committed
503
GST_STATIC_PAD_TEMPLATE ("video_%u",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
504
505
506
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);
David Schleef's avatar
David Schleef committed
507
508

static GstStaticPadTemplate gst_qtdemux_audiosrc_template =
Wim Taymans's avatar
Wim Taymans committed
509
GST_STATIC_PAD_TEMPLATE ("audio_%u",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
510
511
512
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);
Artyom Baginski's avatar
Artyom Baginski committed
513

514
static GstStaticPadTemplate gst_qtdemux_subsrc_template =
Wim Taymans's avatar
Wim Taymans committed
515
GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
516
517
518
519
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
520
521
#define gst_qtdemux_parent_class parent_class
G_DEFINE_TYPE (GstQTDemux, gst_qtdemux, GST_TYPE_ELEMENT);
Artyom Baginski's avatar
Artyom Baginski committed
522

523
static void gst_qtdemux_dispose (GObject * object);
524

525
526
static guint32
gst_qtdemux_find_index_linear (GstQTDemux * qtdemux, QtDemuxStream * str,
527
    GstClockTime media_time);
528
529
530
531
static guint32
gst_qtdemux_find_index_for_given_media_offset_linear (GstQTDemux * qtdemux,
    QtDemuxStream * str, gint64 media_offset);

532
#if 0
533
534
static void gst_qtdemux_set_index (GstElement * element, GstIndex * index);
static GstIndex *gst_qtdemux_get_index (GstElement * element);
535
#endif
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
536
537
static GstStateChangeReturn gst_qtdemux_change_state (GstElement * element,
    GstStateChange transition);
538
539
static void gst_qtdemux_set_context (GstElement * element,
    GstContext * context);
Wim Taymans's avatar
Wim Taymans committed
540
static gboolean qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent);
Wim Taymans's avatar
Wim Taymans committed
541
542
static gboolean qtdemux_sink_activate_mode (GstPad * sinkpad,
    GstObject * parent, GstPadMode mode, gboolean active);
543
544

static void gst_qtdemux_loop (GstPad * pad);
Wim Taymans's avatar
Wim Taymans committed
545
546
547
548
static GstFlowReturn gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent,
    GstBuffer * inbuf);
static gboolean gst_qtdemux_handle_sink_event (GstPad * pad, GstObject * parent,
    GstEvent * event);
Thiago Santos's avatar
Thiago Santos committed
549
static gboolean gst_qtdemux_setcaps (GstQTDemux * qtdemux, GstCaps * caps);
550
551
static gboolean gst_qtdemux_configure_stream (GstQTDemux * qtdemux,
    QtDemuxStream * stream);
552
553
static void gst_qtdemux_stream_check_and_change_stsd_index (GstQTDemux * demux,
    QtDemuxStream * stream);
554
555
static GstFlowReturn gst_qtdemux_process_adapter (GstQTDemux * demux,
    gboolean force);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
556

557
558
static gboolean qtdemux_parse_moov (GstQTDemux * qtdemux,
    const guint8 * buffer, guint length);
559
static gboolean qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node,
560
    const guint8 * buffer, guint length);
561
static gboolean qtdemux_parse_tree (GstQTDemux * qtdemux);
Thiago Santos's avatar
Thiago Santos committed
562
563
static void qtdemux_parse_udta (GstQTDemux * qtdemux, GstTagList * taglist,
    GNode * udta);
564

565
static void gst_qtdemux_handle_esds (GstQTDemux * qtdemux,
566
567
    QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, GNode * esds,
    GstTagList * list);
568
static GstCaps *qtdemux_video_caps (GstQTDemux * qtdemux,
569
570
    QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc,
    const guint8 * stsd_entry_data, gchar ** codec_name);
571
static GstCaps *qtdemux_audio_caps (GstQTDemux * qtdemux,
572
573
574
575
    QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc,
    const guint8 * data, int len, gchar ** codec_name);
static GstCaps *qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
    QtDemuxStreamStsdEntry * entry, guint32 fourcc, const guint8 * data,
576
    gchar ** codec_name);
577
static GstCaps *qtdemux_generic_caps (GstQTDemux * qtdemux,
578
579
    QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc,
    const guint8 * stsd_entry_data, gchar ** codec_name);
580

581
582
static gboolean qtdemux_parse_samples (GstQTDemux * qtdemux,
    QtDemuxStream * stream, guint32 n);
583
static GstFlowReturn qtdemux_expose_streams (GstQTDemux * qtdemux);
584
static QtDemuxStream *gst_qtdemux_stream_ref (QtDemuxStream * stream);
585
static void gst_qtdemux_stream_unref (QtDemuxStream * stream);
586
static void gst_qtdemux_stream_clear (QtDemuxStream * stream);
587
static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux);
588
589
static void qtdemux_do_allocation (QtDemuxStream * stream,
    GstQTDemux * qtdemux);
590
591
592
593
594
static gboolean gst_qtdemux_activate_segment (GstQTDemux * qtdemux,
    QtDemuxStream * stream, guint32 seg_idx, GstClockTime offset);
static gboolean gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux,
    QtDemuxStream * stream, gint seg_idx, GstClockTime offset,
    GstClockTime * _start, GstClockTime * _stop);
595
596
static void gst_qtdemux_send_gap_for_segment (GstQTDemux * demux,
    QtDemuxStream * stream, gint segment_index, GstClockTime pos);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
597

598
static gboolean qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux);
599
static void check_update_duration (GstQTDemux * qtdemux, GstClockTime duration);
600

601
602
603
604
static gchar *qtdemux_uuid_bytes_to_string (gconstpointer uuid_bytes);

static GstStructure *qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux,
    QtDemuxStream * stream, guint sample_index);
605
606
static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
    const gchar * id);
607
static void qtdemux_gst_structure_free (GstStructure * gststructure);
608
static void gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard);
609

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
610
611
static void
gst_qtdemux_class_init (GstQTDemuxClass * klass)
Artyom Baginski's avatar
Artyom Baginski committed
612
613
614
615
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
616
617
  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
Artyom Baginski's avatar
Artyom Baginski committed
618

619
  parent_class = g_type_class_peek_parent (klass);
620

621
622
  gobject_class->dispose = gst_qtdemux_dispose;

623
  gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qtdemux_change_state);
624
#if 0
625
626
  gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_qtdemux_set_index);
  gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_qtdemux_get_index);
627
#endif
628
  gstelement_class->set_context = GST_DEBUG_FUNCPTR (gst_qtdemux_set_context);
629
630

  gst_tag_register_musicbrainz_tags ();
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
631

632
633
634
635
636
637
638
639
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_qtdemux_sink_template);
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_qtdemux_videosrc_template);
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_qtdemux_audiosrc_template);
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_qtdemux_subsrc_template);
640
  gst_element_class_set_static_metadata (gstelement_class, "QuickTime demuxer",
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
641
642
643
644
645
      "Codec/Demuxer",
      "Demultiplex a QuickTime file into audio and video streams",
      "David Schleef <ds@schleef.org>, Wim Taymans <wim@fluendo.com>");

  GST_DEBUG_CATEGORY_INIT (qtdemux_debug, "qtdemux", 0, "qtdemux plugin");
646
  gst_riff_init ();
Artyom Baginski's avatar
Artyom Baginski committed
647
648
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
649
static void
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
650
gst_qtdemux_init (GstQTDemux * qtdemux)
Artyom Baginski's avatar
Artyom Baginski committed
651
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
652
  qtdemux->sinkpad =
653
      gst_pad_new_from_static_template (&gst_qtdemux_sink_template, "sink");
654
  gst_pad_set_activate_function (qtdemux->sinkpad, qtdemux_sink_activate);
Wim Taymans's avatar
Wim Taymans committed
655
656
  gst_pad_set_activatemode_function (qtdemux->sinkpad,
      qtdemux_sink_activate_mode);
657
658
  gst_pad_set_chain_function (qtdemux->sinkpad, gst_qtdemux_chain);
  gst_pad_set_event_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_event);
659
  gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad);
660

661
  qtdemux->adapter = gst_adapter_new ();
662
  g_queue_init (&qtdemux->protection_event_queue);
Thiago Santos's avatar
Thiago Santos committed
663
  qtdemux->flowcombiner = gst_flow_combiner_new ();
664
  g_mutex_init (&qtdemux->expose_lock);
Wim Taymans's avatar
Wim Taymans committed
665

666
667
668
669
670
  qtdemux->active_streams = g_ptr_array_new_with_free_func
      ((GDestroyNotify) gst_qtdemux_stream_unref);
  qtdemux->old_streams = g_ptr_array_new_with_free_func
      ((GDestroyNotify) gst_qtdemux_stream_unref);

Wim Taymans's avatar
Wim Taymans committed
671
  GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE);
672
673

  gst_qtdemux_reset (qtdemux, TRUE);
Artyom Baginski's avatar
Artyom Baginski committed
674
675
}

676
677
678
679
680
681
682
683
684
static void
gst_qtdemux_dispose (GObject * object)
{
  GstQTDemux *qtdemux = GST_QTDEMUX (object);

  if (qtdemux->adapter) {
    g_object_unref (G_OBJECT (qtdemux->adapter));
    qtdemux->adapter = NULL;
  }
685
  gst_tag_list_unref (qtdemux->tag_list);
Thiago Santos's avatar
Thiago Santos committed
686
  gst_flow_combiner_free (qtdemux->flowcombiner);
687
688
689
  g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
      NULL);
  g_queue_clear (&qtdemux->protection_event_queue);
690

691
692
  g_free (qtdemux->cenc_aux_info_sizes);
  qtdemux->cenc_aux_info_sizes = NULL;
693
  g_mutex_clear (&qtdemux->expose_lock);
694

695
696
697
  g_ptr_array_free (qtdemux->active_streams, TRUE);
  g_ptr_array_free (qtdemux->old_streams, TRUE);

698
  G_OBJECT_CLASS (parent_class)->dispose (object);
699
700
}

701
702
703
704
static void
gst_qtdemux_post_no_playable_stream_error (GstQTDemux * qtdemux)
{
  if (qtdemux->posted_redirect) {
705
    GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
706
707
708
        (_("This file contains no playable streams.")),
        ("no known streams found, a redirect message has been posted"));
  } else {
709
    GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
710
711
712
713
714
        (_("This file contains no playable streams.")),
        ("no known streams found"));
  }
}

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
715
716
717
static GstBuffer *
_gst_buffer_new_wrapped (gpointer mem, gsize size, GFreeFunc free_func)
{
Wim Taymans's avatar
Wim Taymans committed
718
719
  return gst_buffer_new_wrapped_full (free_func ? 0 : GST_MEMORY_FLAG_READONLY,
      mem, size, 0, size, mem, free_func);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
720
721
}

722
723
724
725
726
static GstFlowReturn
gst_qtdemux_pull_atom (GstQTDemux * qtdemux, guint64 offset, guint64 size,
    GstBuffer ** buf)
{
  GstFlowReturn flow;
Wim Taymans's avatar
Wim Taymans committed
727
  GstMapInfo map;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
728
  gsize bsize;
729

730
  if (G_UNLIKELY (size == 0)) {
731
732
733
734
735
736
737
    GstFlowReturn ret;
    GstBuffer *tmp = NULL;

    ret = gst_qtdemux_pull_atom (qtdemux, offset, sizeof (guint32), &tmp);
    if (ret != GST_FLOW_OK)
      return ret;

Wim Taymans's avatar
Wim Taymans committed
738
739
    gst_buffer_map (tmp, &map, GST_MAP_READ);
    size = QT_UINT32 (map.data);
740
    GST_DEBUG_OBJECT (qtdemux, "size 0x%08" G_GINT64_MODIFIER "x", size);
741

Wim Taymans's avatar
Wim Taymans committed
742
    gst_buffer_unmap (tmp, &map);
743
744
745
    gst_buffer_unref (tmp);
  }

746
747
  /* Sanity check: catch bogus sizes (fuzzed/broken files) */
  if (G_UNLIKELY (size > QTDEMUX_MAX_ATOM_SIZE)) {
748
749
750
751
752
    if (qtdemux->state != QTDEMUX_STATE_MOVIE && qtdemux->got_moov) {
      /* we're pulling header but already got most interesting bits,
       * so never mind the rest (e.g. tags) (that much) */
      GST_WARNING_OBJECT (qtdemux, "atom has bogus size %" G_GUINT64_FORMAT,
          size);
753
      return GST_FLOW_EOS;
754
755
756
757
758
759
    } else {
      GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
          (_("This file is invalid and cannot be played.")),
          ("atom has bogus size %" G_GUINT64_FORMAT, size));
      return GST_FLOW_ERROR;
    }
760
761
762
763
764
765
766
  }

  flow = gst_pad_pull_range (qtdemux->sinkpad, offset, size, buf);

  if (G_UNLIKELY (flow != GST_FLOW_OK))
    return flow;

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
767
  bsize = gst_buffer_get_size (*buf);
768
  /* Catch short reads - we don't want any partial atoms */
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
769
  if (G_UNLIKELY (bsize < size)) {
770
771
    GST_WARNING_OBJECT (qtdemux,
        "short read: %" G_GSIZE_FORMAT " < %" G_GUINT64_FORMAT, bsize, size);
772
773
    gst_buffer_unref (*buf);
    *buf = NULL;
774
    return GST_FLOW_EOS;
775
776
777
778
779
  }

  return flow;
}

780
#if 1
781
static gboolean
782
783
784
gst_qtdemux_src_convert (GstQTDemux * qtdemux, GstPad * pad,
    GstFormat src_format, gint64 src_value, GstFormat dest_format,
    gint64 * dest_value)
785
786
{
  gboolean res = TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
787
  QtDemuxStream *stream = gst_pad_get_element_private (pad);
788
  gint32 index;
789

Andy Wingo's avatar
Andy Wingo committed
790
791
792
793
  if (stream->subtype != FOURCC_vide) {
    res = FALSE;
    goto done;
  }
794
795
796

  switch (src_format) {
    case GST_FORMAT_TIME:
797
798
799
      switch (dest_format) {
        case GST_FORMAT_BYTES:{
          index = gst_qtdemux_find_index_linear (qtdemux, stream, src_value);
800
801
802
803
          if (-1 == index) {
            res = FALSE;
            goto done;
          }
804
805
806
807
808
809

          *dest_value = stream->samples[index].offset;

          GST_DEBUG_OBJECT (qtdemux, "Format Conversion Time->Offset :%"
              GST_TIME_FORMAT "->%" G_GUINT64_FORMAT,
              GST_TIME_ARGS (src_value), *dest_value);
810
          break;
811
        }
812
813
814
        default:
          res = FALSE;
          break;
815
816
817
      }
      break;
    case GST_FORMAT_BYTES:
818
819
820
821
822
823
      switch (dest_format) {
        case GST_FORMAT_TIME:{
          index =
              gst_qtdemux_find_index_for_given_media_offset_linear (qtdemux,
              stream, src_value);

824
825
826
827
          if (-1 == index) {
            res = FALSE;
            goto done;
          }
828
829

          *dest_value =
830
831
832
833
834
              QTSTREAMTIME_TO_GSTTIME (stream,
              stream->samples[index].timestamp);
          GST_DEBUG_OBJECT (qtdemux,
              "Format Conversion Offset->Time :%" G_GUINT64_FORMAT "->%"
              GST_TIME_FORMAT, src_value, GST_TIME_ARGS (*dest_value));
835
          break;
836
        }
837
838
839
        default:
          res = FALSE;
          break;
840
841
842
843
      }
      break;
    default:
      res = FALSE;
844
      break;
845
846
  }

Andy Wingo's avatar
Andy Wingo committed
847
done:
848
849
  return res;
}
850
#endif
851

852
static gboolean
853
gst_qtdemux_get_duration (GstQTDemux * qtdemux, GstClockTime * duration)
854
{
855
  gboolean res = FALSE;
856
857
858

  *duration = GST_CLOCK_TIME_NONE;

859
860
861
862
863
864
  if (qtdemux->duration != 0 &&
      qtdemux->duration != G_MAXINT64 && qtdemux->timescale != 0) {
    *duration = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration);
    res = TRUE;
  } else {
    *duration = GST_CLOCK_TIME_NONE;
865
  }
866

867
868
869
  return res;
}

870
static gboolean
Wim Taymans's avatar
Wim Taymans committed
871
872
gst_qtdemux_handle_src_query (GstPad * pad, GstObject * parent,
    GstQuery * query)
873
{
874
  gboolean res = FALSE;
Wim Taymans's avatar
Wim Taymans committed
875
  GstQTDemux *qtdemux = GST_QTDEMUX (parent);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
876

877
878
  GST_LOG_OBJECT (pad, "%s query", GST_QUERY_TYPE_NAME (query));

879
  switch (GST_QUERY_TYPE (query)) {
880
881
882
883
884
885
    case GST_QUERY_POSITION:{
      GstFormat fmt;

      gst_query_parse_position (query, &fmt, NULL);
      if (fmt == GST_FORMAT_TIME
          && GST_CLOCK_TIME_IS_VALID (qtdemux->segment.position)) {
886
        gst_query_set_position (query, GST_FORMAT_TIME,
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
887
            qtdemux->segment.position);
Wim Taymans's avatar
Wim Taymans committed
888
889
        res = TRUE;
      }
890
    }
Wim Taymans's avatar
Wim Taymans committed
891
      break;
892
893
    case GST_QUERY_DURATION:{
      GstFormat fmt;
894

895
896
      gst_query_parse_duration (query, &fmt, NULL);
      if (fmt == GST_FORMAT_TIME) {
897
898
899
        /* First try to query upstream */
        res = gst_pad_query_default (pad, parent, query);
        if (!res) {
900
          GstClockTime duration;
901
          if (gst_qtdemux_get_duration (qtdemux, &duration) && duration > 0) {
902
903
904
            gst_query_set_duration (query, GST_FORMAT_TIME, duration);
            res = TRUE;
          }
905
906
907
        }
      }
      break;
908
    }
909
910
    case GST_QUERY_CONVERT:{
      GstFormat src_fmt, dest_fmt;
911
      gint64 src_value, dest_value = 0;
912
913
914

      gst_query_parse_convert (query, &src_fmt, &src_value, &dest_fmt, NULL);

915
      res = gst_qtdemux_src_convert (qtdemux, pad,
916
          src_fmt, src_value, dest_fmt, &dest_value);
Jimmy Ohn's avatar
Jimmy Ohn committed
917
      if (res)
918
        gst_query_set_convert (query, src_fmt, src_value, dest_fmt, dest_value);
Jimmy Ohn's avatar
Jimmy Ohn committed
919

920
921
922
923
924
925
      break;
    }
    case GST_QUERY_FORMATS:
      gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES);
      res = TRUE;
      break;
926
927
    case GST_QUERY_SEEKING:{
      GstFormat fmt;
928
      gboolean seekable;
929

Thiago Santos's avatar
Thiago Santos committed
930
931
932
933
934
935
      /* try upstream first */
      res = gst_pad_query_default (pad, parent, query);

      if (!res) {
        gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
        if (fmt == GST_FORMAT_TIME) {
936
          GstClockTime duration;
Thiago Santos's avatar
Thiago Santos committed
937
938
939
940
941
942
943
944
945
946
947
948
949
950

          gst_qtdemux_get_duration (qtdemux, &duration);
          seekable = TRUE;
          if (!qtdemux->pullbased) {
            GstQuery *q;

            /* we might be able with help from upstream */
            seekable = FALSE;
            q = gst_query_new_seeking (GST_FORMAT_BYTES);
            if (gst_pad_peer_query (qtdemux->sinkpad, q)) {
              gst_query_parse_seeking (q, &fmt, &seekable, NULL, NULL);
              GST_LOG_OBJECT (qtdemux, "upstream BYTE seekable %d", seekable);
            }
            gst_query_unref (q);
951
          }
Thiago Santos's avatar
Thiago Santos committed
952
953
          gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, duration);
          res = TRUE;
954
        }
955
      }
956
      break;
957
    }
Wim Taymans's avatar
Wim Taymans committed
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
    case GST_QUERY_SEGMENT:
    {
      GstFormat format;
      gint64 start, stop;

      format = qtdemux->segment.format;

      start =
          gst_segment_to_stream_time (&qtdemux->segment, format,
          qtdemux->segment.start);
      if ((stop = qtdemux->segment.stop) == -1)
        stop = qtdemux->segment.duration;
      else
        stop = gst_segment_to_stream_time (&qtdemux->segment, format, stop);

      gst_query_set_segment (query, qtdemux->segment.rate, format, start, stop);
      res = TRUE;
      break;
    }
977
    default:
Wim Taymans's avatar
Wim Taymans committed
978
      res = gst_pad_query_default (pad, parent, query);
979
980
981
982
983
984
      break;
  }

  return res;
}

985
986
987
988
989
990
991
static void
gst_qtdemux_push_tags (GstQTDemux * qtdemux, QtDemuxStream * stream)
{
  if (G_LIKELY (stream->pad)) {
    GST_DEBUG_OBJECT (qtdemux, "Checking pad %s:%s for tags",
        GST_DEBUG_PAD_NAME (stream->pad));

992
    if (!gst_tag_list_is_empty (stream->stream_tags)) {
993
      GST_DEBUG_OBJECT (qtdemux, "Sending tags %" GST_PTR_FORMAT,
994
          stream->stream_tags);
995
      gst_pad_push_event (stream->pad,
996
          gst_event_new_tag (gst_tag_list_ref (stream->stream_tags)));
997
998
    }

999
    if (G_UNLIKELY (stream->send_global_tags)) {
1000
1001
1002
      GST_DEBUG_OBJECT (qtdemux, "Sending global tags %" GST_PTR_FORMAT,
          qtdemux->tag_list);
      gst_pad_push_event (stream->pad,
1003
          gst_event_new_tag (gst_tag_list_ref (qtdemux->tag_list)));
1004
1005
1006
1007
1008
      stream->send_global_tags = FALSE;
    }
  }
}

1009
/* push event on all source pads; takes ownership of the event */
1010
static void
1011
gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
1012
{
1013
  gboolean has_valid_stream = FALSE;
1014
  GstEventType etype = GST_EVENT_TYPE (event);
1015
  guint i;
1016
1017
1018
1019

  GST_DEBUG_OBJECT (qtdemux, "pushing %s event on all source pads",
      GST_EVENT_TYPE_NAME (event));

1020
  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
1021
    GstPad *pad;
1022
    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
1023
    GST_DEBUG_OBJECT (qtdemux, "pushing on track-id %u", stream->track_id);
1024

1025
1026
1027
    if ((pad = stream->pad)) {
      has_valid_stream = TRUE;

1028
1029
1030
1031
      if (etype == GST_EVENT_EOS) {
        /* let's not send twice */
        if (stream->sent_eos)
          continue;
1032
        stream->sent_eos = TRUE;
1033
      }
1034
1035

      gst_pad_push_event (pad, gst_event_ref (event));
1036
    }
1037
  }
1038

1039
  gst_event_unref (event);
1040

1041
1042
  /* if it is EOS and there are no pads, post an error */
  if (!has_valid_stream && etype == GST_EVENT_EOS) {
1043
1044
    gst_qtdemux_post_no_playable_stream_error (qtdemux);
  }
1045
1046
}

1047
1048
1049
1050
1051
1052
typedef struct
{
  guint64 media_time;
} FindData;

static gint
1053
find_func (QtDemuxSample * s1, gint64 * media_time, gpointer user_data)
1054
{
1055
  if ((gint64) s1->timestamp > *media_time)
1056
    return 1;
1057
  if ((gint64) s1->timestamp == *media_time)
1058
    return 0;
1059
1060
1061
1062

  return -1;
}

1063
/* find the index of the sample that includes the data for @media_time using a
1064
 * binary search.  Only to be called in optimized cases of linear search below.
1065
 *
1066
 * Returns the index of the sample with the corresponding *DTS*.
1067
 */
1068
1069
1070
static guint32
gst_qtdemux_find_index (GstQTDemux * qtdemux, QtDemuxStream * str,