qtdemux.c 302 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>
Artyom Baginski's avatar
Artyom Baginski committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * 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
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

25 26 27 28
/**
 * SECTION:element-qtdemux
 *
 * Demuxes a .mov file into raw or compressed audio and/or video streams.
29
 *
30 31
 * This element supports both push and pull-based scheduling, depending on the
 * capabilities of the upstream elements.
32 33
 *
 * <refsect2>
34
 * <title>Example launch line</title>
35
 * |[
36
 * gst-launch-1.0 filesrc location=test.mov ! qtdemux name=demux  demux.audio_0 ! decodebin ! audioconvert ! audioresample ! autoaudiosink   demux.video_0 ! queue ! decodebin ! videoconvert ! videoscale ! autovideosink
37
 * ]| Play (parse and decode) a .mov file and try to output it to
38 39 40 41 42 43 44 45
 * 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>
 *
 * Last reviewed on 2006-12-29 (0.10.5)
 */

46 47 48
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
49 50 51

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

52
#include <glib/gprintf.h>
53
#include <gst/tag/tag.h>
Wim Taymans's avatar
Wim Taymans committed
54
#include <gst/audio/audio.h>
55

56
#include "qtatomparser.h"
57 58 59
#include "qtdemux_types.h"
#include "qtdemux_dump.h"
#include "qtdemux_fourcc.h"
60
#include "qtdemux_lang.h"
61
#include "qtdemux.h"
62
#include "qtpalette.h"
63

64 65 66
#include "gst/riff/riff-media.h"
#include "gst/riff/riff-read.h"

67 68
#include <gst/pbutils/pbutils.h>

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

#ifdef HAVE_ZLIB
# include <zlib.h>
#endif
76

77 78 79
/* max. size considered 'sane' for non-mdat atoms */
#define QTDEMUX_MAX_ATOM_SIZE (25*1024*1024)

80 81 82
/* if the sample index is larger than this, something is likely wrong */
#define QTDEMUX_MAX_SAMPLE_INDEX_SIZE (50*1024*1024)

83 84 85 86 87 88
/* 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)

89
GST_DEBUG_CATEGORY (qtdemux_debug);
90

91
/*typedef struct _QtNode QtNode; */
92
typedef struct _QtDemuxSegment QtDemuxSegment;
93
typedef struct _QtDemuxSample QtDemuxSample;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
94

95
/*struct _QtNode
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
96
{
97
  guint32 type;
98
  guint8 *data;
99
  gint len;
100
};*/
101

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
102 103
struct _QtDemuxSample
{
104
  guint32 size;
105
  gint32 pts_offset;            /* Add this value to timestamp to get the pts */
106
  guint64 offset;
107 108
  guint64 timestamp;            /* DTS In mov time */
  guint32 duration;             /* In mov time */
109
  gboolean keyframe;            /* TRUE when this packet is a keyframe */
110 111
};

112 113 114 115 116 117 118 119 120 121 122 123
/* timestamp is the DTS */
#define QTSAMPLE_DTS(stream,sample) gst_util_uint64_scale ((sample)->timestamp,\
    GST_SECOND, (stream)->timescale)
/* timestamp + offset is the PTS */
#define QTSAMPLE_PTS(stream,sample) gst_util_uint64_scale ((sample)->timestamp + \
    (sample)->pts_offset, GST_SECOND, (stream)->timescale)
/* timestamp + duration - dts is the duration */
#define QTSAMPLE_DUR_DTS(stream,sample,dts) (gst_util_uint64_scale ((sample)->timestamp + \
    (sample)->duration, GST_SECOND, (stream)->timescale) - (dts));

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

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
/*
 * 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
 * and are basically tupples of media_time/duration/rate entries. We can have
 * 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
156
 *         0              1              2              3              4
157 158 159 160 161 162
 *           .------------^              ^   .----------^              ^
 *          /              .-------------'  /       .------------------'
 *         /              /          .-----'       /
 *         .--------------.         .--------------.
 *         | segment 1    |         | segment 2    |
 *         '--------------'         '--------------'
Stefan Kost's avatar
Stefan Kost committed
163
 *
164 165 166 167 168 169 170 171 172 173 174
 * The challenge here is to cut out the right pieces of the track for each of
 * the playback segments. This fortunatly can easily be done with the SEGMENT
 * events of gstreamer.
 *
 * 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
175
 *
176 177 178 179 180 181
 * 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.
 */

182 183 184 185 186 187 188 189 190 191 192 193
struct _QtDemuxSegment
{
  /* global time and duration, all gst time */
  guint64 time;
  guint64 stop_time;
  guint64 duration;
  /* media time of trak, all gst time */
  guint64 media_start;
  guint64 media_stop;
  gdouble rate;
};

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
194 195
struct _QtDemuxStream
{
196 197 198
  GstPad *pad;

  /* stream type */
199 200
  guint32 subtype;
  GstCaps *caps;
201
  guint32 fourcc;
202

203 204 205
  /* if the stream has a redirect URI in its headers, we store it here */
  gchar *redirect_uri;

206 207 208
  /* track id */
  guint track_id;

209
  /* duration/scale */
210
  guint64 duration;             /* in timescale */
211
  guint32 timescale;
212

213
  /* language */
214
  gchar lang_id[4];             /* ISO 639-2T language code */
215

216 217 218 219
  /* our samples */
  guint32 n_samples;
  QtDemuxSample *samples;
  gboolean all_keyframe;        /* TRUE when all samples are keyframes (no stss) */
220
  guint32 min_duration;         /* duration in timescale of first sample, used for figuring out
221
                                   the framerate, in timescale units */
222

223 224
  /* if we use chunks or samples */
  gboolean sampled;
225
  guint padding;
226 227

  /* video info */
228 229
  gint width;
  gint height;
230 231 232 233 234
  /* aspect ratio */
  gint display_width;
  gint display_height;
  gint par_w;
  gint par_h;
235 236 237
  /* Numerator/denominator framerate */
  gint fps_n;
  gint fps_d;
238 239
  guint16 bits_per_sample;
  guint16 color_table_id;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
240

241
  /* audio info */
242 243
  gdouble rate;
  gint n_channels;
244 245 246 247
  guint samples_per_packet;
  guint samples_per_frame;
  guint bytes_per_packet;
  guint bytes_per_sample;
248
  guint bytes_per_frame;
249
  guint compression;
250

251 252 253 254 255
  /* allocation */
  gboolean use_allocator;
  GstAllocator *allocator;
  GstAllocationParams params;

256 257
  /* when a discontinuity is pending */
  gboolean discont;
258

259 260 261
  /* list of buffers to push first */
  GSList *buffers;

262 263 264 265
  /* if we need to clip this buffer. This is only needed for uncompressed
   * data */
  gboolean need_clip;

266 267 268
  /* buffer needs some custom processing, e.g. subtitles */
  gboolean need_process;

269 270 271 272 273
  /* current position */
  guint32 segment_index;
  guint32 sample_index;
  guint64 time_position;        /* in gst time */

274 275 276
  /* the Gst segment we are processing out, used for clipping */
  GstSegment segment;

277 278 279
  /* last GstFlowReturn */
  GstFlowReturn last_ret;

280 281 282
  /* quicktime segments */
  guint32 n_segments;
  QtDemuxSegment *segments;
283 284
  guint32 from_sample;
  guint32 to_sample;
285 286

  gboolean sent_eos;
287 288
  GstTagList *pending_tags;
  gboolean send_global_tags;
289 290

  GstEvent *pending_event;
291 292 293 294 295 296 297 298 299 300

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

  gboolean chunks_are_chunks;
301
  gint64 stbl_index;
302 303
  /* stco */
  guint co_size;
304 305 306 307 308 309
  GstByteReader co_chunk;
  guint32 first_chunk;
  guint32 current_chunk;
  guint32 last_chunk;
  guint32 samples_per_chunk;
  guint32 stco_sample_index;
310 311 312
  /* stsz */
  guint32 sample_size;          /* 0 means variable sizes are stored in stsz */
  /* stsc */
313
  guint32 stsc_index;
314
  guint32 n_samples_per_chunk;
315 316 317
  guint32 stsc_chunk_index;
  guint32 stsc_sample_index;
  guint64 chunk_offset;
318
  /* stts */
319 320
  guint32 stts_index;
  guint32 stts_samples;
321
  guint32 n_sample_times;
322
  guint32 stts_sample_index;
323
  guint64 stts_time;
324
  guint32 stts_duration;
325
  /* stss */
326
  gboolean stss_present;
327
  guint32 n_sample_syncs;
328
  guint32 stss_index;
329
  /* stps */
330
  gboolean stps_present;
331
  guint32 n_sample_partial_syncs;
332
  guint32 stps_index;
333 334 335
  /* ctts */
  gboolean ctts_present;
  guint32 n_composition_times;
336 337 338 339
  guint32 ctts_index;
  guint32 ctts_sample_index;
  guint32 ctts_count;
  gint32 ctts_soffset;
340 341 342 343 344 345

  /* fragmented */
  gboolean parsed_trex;
  guint32 def_sample_duration;
  guint32 def_sample_size;
  guint32 def_sample_flags;
346 347
};

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
348 349
enum QtDemuxState
{
350 351 352
  QTDEMUX_STATE_INITIAL,        /* Initial state (haven't got the header yet) */
  QTDEMUX_STATE_HEADER,         /* Parsing the header */
  QTDEMUX_STATE_MOVIE,          /* Parsing/Playing the media data */
353
  QTDEMUX_STATE_BUFFER_MDAT     /* Buffering the mdat atom */
354 355
};

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
356
static GNode *qtdemux_tree_get_child_by_type (GNode * node, guint32 fourcc);
357
static GNode *qtdemux_tree_get_child_by_type_full (GNode * node,
358
    guint32 fourcc, GstByteReader * parser);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
359
static GNode *qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc);
360 361
static GNode *qtdemux_tree_get_sibling_by_type_full (GNode * node,
    guint32 fourcc, GstByteReader * parser);
362

David Schleef's avatar
David Schleef committed
363
static GstStaticPadTemplate gst_qtdemux_sink_template =
364
    GST_STATIC_PAD_TEMPLATE ("sink",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
365
    GST_PAD_SINK,
366
    GST_PAD_ALWAYS,
367 368
    GST_STATIC_CAPS ("video/quicktime; video/mj2; audio/x-m4a; "
        "application/x-3gp")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
369
    );
David Schleef's avatar
David Schleef committed
370 371

static GstStaticPadTemplate gst_qtdemux_videosrc_template =
Wim Taymans's avatar
Wim Taymans committed
372
GST_STATIC_PAD_TEMPLATE ("video_%u",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
373 374 375
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);
David Schleef's avatar
David Schleef committed
376 377

static GstStaticPadTemplate gst_qtdemux_audiosrc_template =
Wim Taymans's avatar
Wim Taymans committed
378
GST_STATIC_PAD_TEMPLATE ("audio_%u",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
379 380 381
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);
Artyom Baginski's avatar
Artyom Baginski committed
382

383
static GstStaticPadTemplate gst_qtdemux_subsrc_template =
Wim Taymans's avatar
Wim Taymans committed
384
GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
385 386 387 388
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
389 390
#define gst_qtdemux_parent_class parent_class
G_DEFINE_TYPE (GstQTDemux, gst_qtdemux, GST_TYPE_ELEMENT);
Artyom Baginski's avatar
Artyom Baginski committed
391

392
static void gst_qtdemux_dispose (GObject * object);
393

394 395 396 397 398 399 400
static guint32
gst_qtdemux_find_index_linear (GstQTDemux * qtdemux, QtDemuxStream * str,
    guint64 media_time);
static guint32
gst_qtdemux_find_index_for_given_media_offset_linear (GstQTDemux * qtdemux,
    QtDemuxStream * str, gint64 media_offset);

401
#if 0
402 403
static void gst_qtdemux_set_index (GstElement * element, GstIndex * index);
static GstIndex *gst_qtdemux_get_index (GstElement * element);
404
#endif
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
405 406
static GstStateChangeReturn gst_qtdemux_change_state (GstElement * element,
    GstStateChange transition);
Wim Taymans's avatar
Wim Taymans committed
407
static gboolean qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent);
Wim Taymans's avatar
Wim Taymans committed
408 409
static gboolean qtdemux_sink_activate_mode (GstPad * sinkpad,
    GstObject * parent, GstPadMode mode, gboolean active);
410 411

static void gst_qtdemux_loop (GstPad * pad);
Wim Taymans's avatar
Wim Taymans committed
412 413 414 415
static GstFlowReturn gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent,
    GstBuffer * inbuf);
static gboolean gst_qtdemux_handle_sink_event (GstPad * pad, GstObject * parent,
    GstEvent * event);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
416

417 418
static gboolean qtdemux_parse_moov (GstQTDemux * qtdemux,
    const guint8 * buffer, guint length);
419
static gboolean qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node,
420
    const guint8 * buffer, guint length);
421
static gboolean qtdemux_parse_tree (GstQTDemux * qtdemux);
422

423
static void gst_qtdemux_handle_esds (GstQTDemux * qtdemux,
424
    QtDemuxStream * stream, GNode * esds, GstTagList * list);
425 426
static GstCaps *qtdemux_video_caps (GstQTDemux * qtdemux,
    QtDemuxStream * stream, guint32 fourcc, const guint8 * stsd_data,
427
    gchar ** codec_name);
428 429
static GstCaps *qtdemux_audio_caps (GstQTDemux * qtdemux,
    QtDemuxStream * stream, guint32 fourcc, const guint8 * data, int len,
430
    gchar ** codec_name);
431
static GstCaps *qtdemux_sub_caps (GstQTDemux * qtdemux,
432 433
    QtDemuxStream * stream, guint32 fourcc, const guint8 * data,
    gchar ** codec_name);
434 435
static gboolean qtdemux_parse_samples (GstQTDemux * qtdemux,
    QtDemuxStream * stream, guint32 n);
436
static GstFlowReturn qtdemux_expose_streams (GstQTDemux * qtdemux);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
437 438 439

static void
gst_qtdemux_class_init (GstQTDemuxClass * klass)
Artyom Baginski's avatar
Artyom Baginski committed
440 441 442 443
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
444 445
  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
Artyom Baginski's avatar
Artyom Baginski committed
446

447
  parent_class = g_type_class_peek_parent (klass);
448

449 450
  gobject_class->dispose = gst_qtdemux_dispose;

451
  gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qtdemux_change_state);
452
#if 0
453 454
  gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_qtdemux_set_index);
  gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_qtdemux_get_index);
455
#endif
456 457

  gst_tag_register_musicbrainz_tags ();
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
458 459 460 461 462 463 464 465 466

  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_qtdemux_sink_template));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_qtdemux_videosrc_template));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_qtdemux_audiosrc_template));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_qtdemux_subsrc_template));
467
  gst_element_class_set_static_metadata (gstelement_class, "QuickTime demuxer",
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
468 469 470 471 472 473
      "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");

Artyom Baginski's avatar
Artyom Baginski committed
474 475
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
476
static void
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
477
gst_qtdemux_init (GstQTDemux * qtdemux)
Artyom Baginski's avatar
Artyom Baginski committed
478
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
479
  qtdemux->sinkpad =
480
      gst_pad_new_from_static_template (&gst_qtdemux_sink_template, "sink");
481
  gst_pad_set_activate_function (qtdemux->sinkpad, qtdemux_sink_activate);
Wim Taymans's avatar
Wim Taymans committed
482 483
  gst_pad_set_activatemode_function (qtdemux->sinkpad,
      qtdemux_sink_activate_mode);
484 485
  gst_pad_set_chain_function (qtdemux->sinkpad, gst_qtdemux_chain);
  gst_pad_set_event_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_event);
486
  gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad);
487

488 489
  qtdemux->state = QTDEMUX_STATE_INITIAL;
  qtdemux->pullbased = FALSE;
490
  qtdemux->posted_redirect = FALSE;
491 492 493
  qtdemux->neededbytes = 16;
  qtdemux->todrop = 0;
  qtdemux->adapter = gst_adapter_new ();
494
  qtdemux->offset = 0;
495
  qtdemux->first_mdat = -1;
496
  qtdemux->got_moov = FALSE;
497 498
  qtdemux->mdatoffset = GST_CLOCK_TIME_NONE;
  qtdemux->mdatbuffer = NULL;
499
  gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
Wim Taymans's avatar
Wim Taymans committed
500 501

  GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE);
Artyom Baginski's avatar
Artyom Baginski committed
502 503
}

504 505 506 507 508 509 510 511 512
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;
  }
513 514

  G_OBJECT_CLASS (parent_class)->dispose (object);
515 516
}

517 518 519 520
static void
gst_qtdemux_post_no_playable_stream_error (GstQTDemux * qtdemux)
{
  if (qtdemux->posted_redirect) {
521
    GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
522 523 524
        (_("This file contains no playable streams.")),
        ("no known streams found, a redirect message has been posted"));
  } else {
525
    GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
526 527 528 529 530
        (_("This file contains no playable streams.")),
        ("no known streams found"));
  }
}

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
531
static void
Wim Taymans's avatar
Wim Taymans committed
532 533
_gst_buffer_copy_into_mem (GstBuffer * dest, gsize offset, const guint8 * src,
    gsize size)
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
534 535 536 537 538 539 540 541
{
  gsize bsize;

  g_return_if_fail (gst_buffer_is_writable (dest));

  bsize = gst_buffer_get_size (dest);
  g_return_if_fail (bsize >= offset + size);

Wim Taymans's avatar
Wim Taymans committed
542
  gst_buffer_fill (dest, offset, src, size);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
543 544 545 546 547 548 549 550
}

static GstBuffer *
_gst_buffer_new_wrapped (gpointer mem, gsize size, GFreeFunc free_func)
{
  GstBuffer *buf;

  buf = gst_buffer_new ();
Wim Taymans's avatar
Wim Taymans committed
551
  gst_buffer_append_memory (buf,
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
552
      gst_memory_new_wrapped (free_func ? 0 : GST_MEMORY_FLAG_READONLY,
Wim Taymans's avatar
Wim Taymans committed
553
          mem, size, 0, size, mem, free_func));
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
554 555 556 557

  return buf;
}

558 559 560 561 562
static GstFlowReturn
gst_qtdemux_pull_atom (GstQTDemux * qtdemux, guint64 offset, guint64 size,
    GstBuffer ** buf)
{
  GstFlowReturn flow;
Wim Taymans's avatar
Wim Taymans committed
563
  GstMapInfo map;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
564
  gsize bsize;
565

566
  if (G_UNLIKELY (size == 0)) {
567 568 569 570 571 572 573
    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
574 575
    gst_buffer_map (tmp, &map, GST_MAP_READ);
    size = QT_UINT32 (map.data);
576
    GST_DEBUG_OBJECT (qtdemux, "size 0x%08" G_GINT64_MODIFIER "x", size);
577

Wim Taymans's avatar
Wim Taymans committed
578
    gst_buffer_unmap (tmp, &map);
579 580 581
    gst_buffer_unref (tmp);
  }

582 583
  /* Sanity check: catch bogus sizes (fuzzed/broken files) */
  if (G_UNLIKELY (size > QTDEMUX_MAX_ATOM_SIZE)) {
584 585 586 587 588
    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);
589
      return GST_FLOW_EOS;
590 591 592 593 594 595
    } 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;
    }
596 597 598 599 600 601 602
  }

  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
603
  bsize = gst_buffer_get_size (*buf);
604
  /* Catch short reads - we don't want any partial atoms */
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
605
  if (G_UNLIKELY (bsize < size)) {
606 607
    GST_WARNING_OBJECT (qtdemux,
        "short read: %" G_GSIZE_FORMAT " < %" G_GUINT64_FORMAT, bsize, size);
608 609
    gst_buffer_unref (*buf);
    *buf = NULL;
610
    return GST_FLOW_EOS;
611 612 613 614 615
  }

  return flow;
}

616
#if 1
617
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
618
gst_qtdemux_src_convert (GstPad * pad, GstFormat src_format, gint64 src_value,
619
    GstFormat dest_format, gint64 * dest_value)
620 621
{
  gboolean res = TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
622
  QtDemuxStream *stream = gst_pad_get_element_private (pad);
623 624
  GstQTDemux *qtdemux = GST_QTDEMUX (gst_pad_get_parent (pad));
  gint32 index;
625

Andy Wingo's avatar
Andy Wingo committed
626 627 628 629
  if (stream->subtype != FOURCC_vide) {
    res = FALSE;
    goto done;
  }
630 631 632

  switch (src_format) {
    case GST_FORMAT_TIME:
633 634 635 636 637 638 639 640 641 642 643
      switch (dest_format) {
        case GST_FORMAT_BYTES:{
          index = gst_qtdemux_find_index_linear (qtdemux, stream, src_value);
          if (-1 == index)
            return FALSE;

          *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);
644
          break;
645
        }
646 647 648
        default:
          res = FALSE;
          break;
649 650 651
      }
      break;
    case GST_FORMAT_BYTES:
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
      switch (dest_format) {
        case GST_FORMAT_TIME:{
          index =
              gst_qtdemux_find_index_for_given_media_offset_linear (qtdemux,
              stream, src_value);

          if (-1 == index)
            return FALSE;

          *dest_value =
              gst_util_uint64_scale (stream->samples[index].timestamp,
              GST_SECOND, stream->timescale);
          GST_DEBUG_OBJECT (qtdemux, "Format Conversion Offset->Time :%"
              G_GUINT64_FORMAT "->%" GST_TIME_FORMAT,
              src_value, GST_TIME_ARGS (*dest_value));
667
          break;
668
        }
669 670 671
        default:
          res = FALSE;
          break;
672 673 674 675 676 677
      }
      break;
    default:
      res = FALSE;
  }

Andy Wingo's avatar
Andy Wingo committed
678 679 680
done:
  gst_object_unref (qtdemux);

681 682
  return res;
}
683
#endif
684

685 686 687 688 689 690 691 692
static gboolean
gst_qtdemux_get_duration (GstQTDemux * qtdemux, gint64 * duration)
{
  gboolean res = TRUE;

  *duration = GST_CLOCK_TIME_NONE;

  if (qtdemux->duration != 0) {
693
    if (qtdemux->duration != G_MAXINT64 && qtdemux->timescale != 0) {
694
      *duration = gst_util_uint64_scale (qtdemux->duration,
695 696 697 698 699 700
          GST_SECOND, qtdemux->timescale);
    }
  }
  return res;
}

701
static gboolean
Wim Taymans's avatar
Wim Taymans committed
702 703
gst_qtdemux_handle_src_query (GstPad * pad, GstObject * parent,
    GstQuery * query)
704
{
705
  gboolean res = FALSE;
Wim Taymans's avatar
Wim Taymans committed
706
  GstQTDemux *qtdemux = GST_QTDEMUX (parent);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
707

708 709
  GST_LOG_OBJECT (pad, "%s query", GST_QUERY_TYPE_NAME (query));

710
  switch (GST_QUERY_TYPE (query)) {
711
    case GST_QUERY_POSITION:
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
712
      if (GST_CLOCK_TIME_IS_VALID (qtdemux->segment.position)) {
713
        gst_query_set_position (query, GST_FORMAT_TIME,
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
714
            qtdemux->segment.position);
Wim Taymans's avatar
Wim Taymans committed
715 716 717
        res = TRUE;
      }
      break;
718 719
    case GST_QUERY_DURATION:{
      GstFormat fmt;
720

721 722
      gst_query_parse_duration (query, &fmt, NULL);
      if (fmt == GST_FORMAT_TIME) {
723 724 725 726 727 728 729 730 731 732
        /* First try to query upstream */
        res = gst_pad_query_default (pad, parent, query);
        if (!res) {
          gint64 duration = -1;

          gst_qtdemux_get_duration (qtdemux, &duration);
          if (duration > 0) {
            gst_query_set_duration (query, GST_FORMAT_TIME, duration);
            res = TRUE;
          }
733 734 735
        }
      }
      break;
736
    }
737 738
    case GST_QUERY_CONVERT:{
      GstFormat src_fmt, dest_fmt;
739
      gint64 src_value, dest_value = 0;
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754

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

      res = gst_qtdemux_src_convert (pad,
          src_fmt, src_value, dest_fmt, &dest_value);
      if (res) {
        gst_query_set_convert (query, src_fmt, src_value, dest_fmt, dest_value);
        res = TRUE;
      }
      break;
    }
    case GST_QUERY_FORMATS:
      gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES);
      res = TRUE;
      break;
755 756
    case GST_QUERY_SEEKING:{
      GstFormat fmt;
757
      gboolean seekable;
758 759 760 761 762 763

      gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
      if (fmt == GST_FORMAT_TIME) {
        gint64 duration = -1;

        gst_qtdemux_get_duration (qtdemux, &duration);
764 765 766 767 768 769 770
        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);
771
          if (gst_pad_peer_query (qtdemux->sinkpad, q)) {
772 773 774 775 776 777
            gst_query_parse_seeking (q, &fmt, &seekable, NULL, NULL);
            GST_LOG_OBJECT (qtdemux, "upstream BYTE seekable %d", seekable);
          }
          gst_query_unref (q);
        }
        gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, duration);
778 779
        res = TRUE;
      }
780
      break;
781
    }
782
    default:
Wim Taymans's avatar
Wim Taymans committed
783
      res = gst_pad_query_default (pad, parent, query);
784 785 786 787 788 789
      break;
  }

  return res;
}

790 791 792 793 794 795 796 797 798 799 800
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));

    if (G_UNLIKELY (stream->pending_tags)) {
      GST_DEBUG_OBJECT (qtdemux, "Sending tags %" GST_PTR_FORMAT,
          stream->pending_tags);
      gst_pad_push_event (stream->pad,
801
          gst_event_new_tag (stream->pending_tags));
802 803 804 805 806 807 808
      stream->pending_tags = NULL;
    }

    if (G_UNLIKELY (stream->send_global_tags && qtdemux->tag_list)) {
      GST_DEBUG_OBJECT (qtdemux, "Sending global tags %" GST_PTR_FORMAT,
          qtdemux->tag_list);
      gst_pad_push_event (stream->pad,
809
          gst_event_new_tag (gst_tag_list_ref (qtdemux->tag_list)));
810 811 812 813 814
      stream->send_global_tags = FALSE;
    }
  }
}

815
/* push event on all source pads; takes ownership of the event */
816
static void
817
gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
818 819
{
  guint n;
820
  gboolean has_valid_stream = FALSE;
821
  GstEventType etype = GST_EVENT_TYPE (event);
822 823 824 825 826

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

  for (n = 0; n < qtdemux->n_streams; n++) {
827
    GstPad *pad;
828
    QtDemuxStream *stream = qtdemux->streams[n];
829

830 831 832
    if ((pad = stream->pad)) {
      has_valid_stream = TRUE;

833 834 835 836
      if (etype == GST_EVENT_EOS) {
        /* let's not send twice */
        if (stream->sent_eos)
          continue;
837
        stream->sent_eos = TRUE;
838
      }
839 840

      gst_pad_push_event (pad, gst_event_ref (event));
841
    }
842
  }
843

844
  gst_event_unref (event);
845

846 847
  /* if it is EOS and there are no pads, post an error */
  if (!has_valid_stream && etype == GST_EVENT_EOS) {
848 849
    gst_qtdemux_post_no_playable_stream_error (qtdemux);
  }
850 851
}

852 853 854 855 856 857 858 859 860 861
/* push a pending newsegment event, if any from the streaming thread */
static void
gst_qtdemux_push_pending_newsegment (GstQTDemux * qtdemux)
{
  if (qtdemux->pending_newsegment) {
    gst_qtdemux_push_event (qtdemux, qtdemux->pending_newsegment);
    qtdemux->pending_newsegment = NULL;
  }
}

862 863 864 865 866 867 868 869 870 871 872 873 874 875
typedef struct
{
  guint64 media_time;
} FindData;

static gint
find_func (QtDemuxSample * s1, guint64 * media_time, gpointer user_data)
{
  if (s1->timestamp > *media_time)
    return 1;

  return -1;
}

876
/* find the index of the sample that includes the data for @media_time using a
877
 * binary search.  Only to be called in optimized cases of linear search below.
878
 *
879
 * Returns the index of the sample.
880
 */
881 882 883
static guint32
gst_qtdemux_find_index (GstQTDemux * qtdemux, QtDemuxStream * str,
    guint64 media_time)
884
{
885 886
  QtDemuxSample *result;
  guint32 index;
887

888
  /* convert media_time to mov format */
889 890
  media_time =
      gst_util_uint64_scale_ceil (media_time, str->timescale, GST_SECOND);
891

892
  result = gst_util_array_binary_search (str->samples, str->stbl_index + 1,
893 894
      sizeof (QtDemuxSample), (GCompareDataFunc) find_func,
      GST_SEARCH_MODE_BEFORE, &media_time, NULL);
895

896 897 898 899 900 901
  if (G_LIKELY (result))
    index = result - str->samples;
  else
    index = 0;

  return index;
902
}
903

904 905 906 907 908 909 910 911 912 913 914 915 916 917


/* find the index of the sample that includes the data for @media_offset using a
 * linear search
 *
 * Returns the index of the sample.
 */
static guint32
gst_qtdemux_find_index_for_given_media_offset_linear (GstQTDemux * qtdemux,
    QtDemuxStream * str, gint64 media_offset)
{
  QtDemuxSample *result = str->samples;
  guint32 index = 0;

918 919 920
  if (result == NULL || str->n_samples == 0)
    return -1;

921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944
  if (media_offset == result->offset)
    return index;

  result++;
  while (index < str->n_samples - 1) {
    if (!qtdemux_parse_samples (qtdemux, str, index + 1))
      goto parse_failed;

    if (media_offset < result->offset)
      break;

    index++;
    result++;
  }
  return index;

  /* ERRORS */
parse_failed:
  {
    GST_LOG_OBJECT (qtdemux, "Parsing of index %u failed!", index + 1);
    return -1;
  }
}

945
/* find the index of the sample that includes the data for @media_time using a
946 947
 * linear search, and keeping in mind that not all samples may have been parsed
 * yet.  If possible, it will delegate to binary search.
948 949 950 951 952 953 954 955
 *
 * Returns the index of the sample.
 */
static guint32
gst_qtdemux_find_index_linear (GstQTDemux * qtdemux, QtDemuxStream * str,
    guint64 media_time)
{
  guint32 index = 0;
956
  guint64 mov_time;
957

958
  /* convert media_time to mov format */
959
  mov_time =
960
      gst_util_uint64_scale_ceil (media_time, str->timescale, GST_SECOND);
961

962
  if (mov_time == str->samples[0].timestamp)
963 964
    return index;

965 966 967 968 969
  /* use faster search if requested time in already parsed range */
  if (str->stbl_index >= 0 &&
      mov_time <= str->samples[str->stbl_index].timestamp)
    return gst_qtdemux_find_index (qtdemux, str, media_time);

970
  while (index < str->n_samples - 1) {
971
    if (!qtdemux_parse_samples (qtdemux, str, index + 1))
972 973
      goto parse_failed;

974
    if (mov_time < str->samples[index + 1].timestamp)
975
      break;
976

977 978 979
    index++;
  }
  return index;
980 981 982 983 984 985 986

  /* ERRORS */
parse_failed:
  {
    GST_LOG_OBJECT (qtdemux, "Parsing of index %u failed!", index + 1);
    return -1;
  }
987 988
}

989 990 991 992 993 994 995 996 997
/* find the index of the keyframe needed to decode the sample at @index
 * of stream @str.
 *
 * Returns the index of the keyframe.
 */
static guint32
gst_qtdemux_find_keyframe (GstQTDemux * qtdemux, QtDemuxStream * str,
    guint32 index)
{
998 999 1000 1001 1002 1003
  guint32 new_index = index;

  if (index >= str->n_samples) {
    new_index = str->n_samples;
    goto beach;
  }
1004 1005

  /* all keyframes, return index */
1006 1007 1008 1009
  if (str->all_keyframe) {
    new_index = index;
    goto beach;
  }
1010 1011 1012

  /* else go back until we have a keyframe */
  while (TRUE) {
1013
    if (str->samples[new_index].keyframe)
1014 1015
      break;

1016
    if (new_index == 0)
1017 1018
      break;

1019
    new_index--;
1020
  }
1021 1022 1023 1024 1025 1026

beach:
  GST_DEBUG_OBJECT (qtdemux, "searching for keyframe index before index %u "
      "gave %u", index, new_index);

  return new_index;
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
}

/* find the segment for @time_position for @stream
 *
 * Returns -1 if the segment cannot be found.
 */
static guint32
gst_qtdemux_find_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
    guint64 time_position)
{
  gint i;
  guint32 seg_idx;

1040 1041 1042
  GST_LOG_OBJECT (qtdemux, "finding segment for %" GST_TIME_FORMAT,
      GST_TIME_ARGS (time_position));

1043 1044 1045 1046 1047 1048
  /* find segment corresponding to time_position if we are looking
   * for a segment. */
  seg_idx = -1;
  for (i = 0; i < stream->n_segments; i++) {
    QtDemuxSegment *segment = &stream->segments[i];

1049 1050 1051 1052
    GST_LOG_OBJECT (qtdemux,
        "looking at segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT,
        GST_TIME_ARGS (segment->time), GST_TIME_ARGS (segment->stop_time));

1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
    /* For the last segment we include stop_time in the last segment */
    if (i < stream->n_segments - 1) {
      if (segment->time <= time_position && time_position < segment->stop_time) {
        GST_LOG_OBJECT (qtdemux, "segment %d matches", i);
        seg_idx = i;
        break;
      }
    } else {
      if (segment->time <= time_position && time_position <= segment->stop_time) {
        GST_LOG_OBJECT (qtdemux, "segment %d matches", i);
        seg_idx = i;
        break;
      }
1066
    }
1067
  }
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
  return seg_idx;
}

/* move the stream @str to the sample position @index.
 *
 * Updates @str->sample_index and marks discontinuity if needed.
 */
static void
gst_qtdemux_move_stream (GstQTDemux * qtdemux, QtDemuxStream * str,
    guint32 index)
{
  /* no change needed */
  if (index == str->sample_index)
    return;

  GST_DEBUG_OBJECT (qtdemux, "moving to sample %u of %u", index,
      str->n_samples);

  /* position changed, we have a discont */
  str->sample_index = index;
Stefan Kost's avatar
Stefan Kost committed
1088
  /* Each time we move in the stream we store the position where we are
1089 1090
   * starting from */
  str->from_sample = index;
1091
  str->discont = TRUE;
1092 1093
}

1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
static void
gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time,
    gint64 * key_time, gint64 * key_offset)
{
  guint64 min_offset;
  gint64 min_byte_offset = -1;
  gint n;

  min_offset = desired_time;

  /* for each stream, find the index of the sample in the segment
   * and move back to the previous keyframe. */
  for (n = 0; n < qtdemux->n_streams; n++) {
    QtDemuxStream *str;
    guint32 index, kindex;
    guint32 seg_idx;
    guint64 media_start;
    guint64 media_time;
    guint64 seg_time;
    QtDemuxSegment *seg;

    str = qtdemux->streams[n];

    seg_idx = gst_qtdemux_find_segment (qtdemux, str, desired_time);
    GST_DEBUG_OBJECT (qtdemux, "align segment %d", seg_idx);

    /* segment not found, continue with normal flow */
    if (seg_idx == -1)
      continue;

    /* get segment and time in the segment */
    seg = &str->segments[seg_idx];
    seg_time = desired_time - seg->time;

    /* get the media time in the segment */
    media_start = seg->media_start + seg_time;

    /* get the index of the sample with media time */
1132
    index = gst_qtdemux_find_index_linear (qtdemux, str, media_start);
1133 1134 1135
    GST_DEBUG_OBJECT (qtdemux, "sample for %" GST_TIME_FORMAT " at %u"
        " at offset %" G_GUINT64_FORMAT,
        GST_TIME_ARGS (media_start), index, str->samples[index].offset);
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145

    /* find previous keyframe */
    kindex = gst_qtdemux_find_keyframe (qtdemux, str, index);

    /* if the keyframe is at a different position, we need to update the
     * requested seek time */
    if (index != kindex) {
      index = kindex;

      /* get timestamp of keyframe */
1146 1147 1148
      media_time =
          gst_util_uint64_scale (str->samples[kindex].timestamp, GST_SECOND,
          str->timescale);
1149 1150 1151
      GST_DEBUG_OBJECT (qtdemux, "keyframe at %u with time %" GST_TIME_FORMAT
          " at offset %" G_GUINT64_FORMAT,
          kindex, GST_TIME_ARGS (media_time), str->samples[kindex].offset);
1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191

      /* keyframes in the segment get a chance to change the
       * desired_offset. keyframes out of the segment are
       * ignored. */
      if (media_time >= seg->media_start) {
        guint64 seg_time;

        /* this keyframe is inside the segment, convert back to
         * segment time */
        seg_time = (media_time - seg->media_start) + seg->time;
        if (seg_time < min_offset)
          min_offset = seg_time;
      }
    }

    if (min_byte_offset < 0 || str->samples[index].offset < min_byte_offset)
      min_byte_offset = str->samples[index].offset;
  }

  if (key_time)
    *key_time = min_offset;
  if (key_offset)
    *key_offset = min_byte_offset;
}

static gboolean
gst_qtdemux_convert_seek (GstPad * pad, GstFormat * format,
    GstSeekType cur_type, gint64 * cur, GstSeekType stop_type, gint64 * stop)
{
  gboolean res;

  g_return_val_if_fail (format != NULL, FALSE);
  g_return_val_if_fail (cur != NULL, FALSE);
  g_return_val_if_fail (stop != NULL, FALSE);

  if (*format == GST_FORMAT_TIME)
    return TRUE;

  res = TRUE;
  if (cur_type != GST_SEEK_TYPE_NONE)
1192
    res = gst_pad_query_convert (pad, *format, *cur, GST_FORMAT_TIME, cur);
1193
  if (res && stop_type != GST_SEEK_TYPE_NONE)
1194
    res = gst_pad_query_convert (pad, *format, *stop, GST_FORMAT_TIME, stop);
1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206