gstoggdemux.c 53.4 KB
Newer Older
1
/* GStreamer
2
 * Copyright (C) 2003, 2004 Benjamin Otte <otte@gnome.org>
3
 *
4
 * gstoggdemux.c: ogg stream demuxer
5 6 7 8 9 10 11 12 13 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
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
26
#include <gst/bytestream/filepad.h>
27
#include <ogg/ogg.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
28
#include <string.h>
29

30 31 32 33 34 35 36 37 38 39 40 41 42
/* tweak this to improve setup times */
/* PLEASE don't just tweak it because one file is faster with tweaked numbers, 
 * but use a good benchmark with both video and audio files */
/* number of bytes we seek in front of desired point so we can resync properly */
#define SETUP_EXPECTED_PAGE_SIZE (8500) /* this is out of vorbisfile */
/* number of bytes where we don't seek to middle anymore but just walk through
 * all packets */
#define SETUP_PASSTHROUGH_SIZE (SETUP_EXPECTED_PAGE_SIZE * 20)
/* if we have to repeat a seek backwards because we didn't seek back far enough, 
 * we multiply the amount we seek by this amount */
#define SETUP_SEEK_MULTIPLIER (5)


43
GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_debug);
44
GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_setup_debug);
45 46
#define GST_CAT_DEFAULT gst_ogg_demux_debug

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
47
#define GST_TYPE_OGG_DEMUX (gst_ogg_demux_get_type())
48 49 50 51 52 53 54 55
#define GST_OGG_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_DEMUX, GstOggDemux))
#define GST_OGG_DEMUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_DEMUX, GstOggDemux))
#define GST_IS_OGG_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_DEMUX))
#define GST_IS_OGG_DEMUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_DEMUX))

typedef struct _GstOggDemux GstOggDemux;
typedef struct _GstOggDemuxClass GstOggDemuxClass;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
56 57
typedef enum
{
58 59 60 61 62 63 64 65 66 67
  /* just because you shouldn't make a valid enum value 0 */
  GST_OGG_STATE_INAVLID,
  /* just started, we need to decide if we should do setup */
  GST_OGG_STATE_START,
  /* setup is analyzing the stream, getting lengths and so on */
  GST_OGG_STATE_SETUP,
  /* after a seek, during resyncing */
  GST_OGG_STATE_SEEK,
  /* normal playback */
  GST_OGG_STATE_PLAY
68 69
}
GstOggState;
70 71

/* all information needed for one ogg stream */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
72 73
typedef struct
{
74
  GstPad *pad;                  /* reference for this pad is held by element we belong to */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
75 76 77

  gint serial;
  ogg_stream_state stream;
78 79 80
  guint64 offset;               /* end offset of last buffer */
  guint64 known_offset;         /* last known offset */
  gint64 packetno;              /* number of next expected packet */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
81

82
  guint64 start;                /* first valid granulepos */
83 84
  guint64 length;               /* length of stream or 0 */
  glong pages;                  /* number of pages in stream or 0 */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
85

86 87 88 89 90
  gint64 start_offset;          /* earliest offset in file where this stream has been found */
  gboolean start_found;         /* we have found the bos (first) page */
  gint64 end_offset;            /* last offset in file where this stream has been found */
  gboolean end_found;           /* we have fount the eos (last) page */

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
91
  guint flags;
92 93
}
GstOggPad;
94

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
95 96 97 98
typedef enum
{
  GST_OGG_PAD_NEEDS_DISCONT = (1 << 0),
  GST_OGG_PAD_NEEDS_FLUSH = (1 << 1)
99 100 101
}
GstOggPadFlags;

102
/* all information needed for one ogg chain (relevant for chained bitstreams) */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
103 104
typedef struct
{
105 106 107
  gint64 starts_at;             /* starting offset of chain */
  gint64 ends_at;               /* end offset of stream (only valid when not last chain or not in setup) */

108 109 110
  GSList *pads;                 /* list of GstOggPad */
}
GstOggChain;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
111

112
#define CURRENT_CHAIN(ogg) (&g_array_index ((ogg)->chains, GstOggChain, (ogg)->current_chain))
113 114 115
#define FOR_PAD_IN_CURRENT_CHAIN(ogg, __pad, ...) \
  FOR_PAD_IN_CHAIN(ogg, __pad, (ogg)->current_chain, __VA_ARGS__)
#define FOR_PAD_IN_CHAIN(ogg, _pad, i, ...) G_STMT_START{			\
116
  GSList *_walk;							      	\
117 118 119
  GstOggChain *_chain = &g_array_index ((ogg)->chains, GstOggChain, i);		\
  if (i != -1) {								\
    for (_walk = _chain->pads; _walk; _walk = g_slist_next (_walk)) {		\
120 121 122
      GstOggPad *_pad = (GstOggPad *) _walk->data;				\
      __VA_ARGS__								\
    }										\
123 124
  }										\
}G_STMT_END
125

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
126 127
typedef enum
{
128 129 130
  GST_OGG_FLAG_BOS = GST_ELEMENT_FLAG_LAST,
  GST_OGG_FLAG_EOS,
  GST_OGG_FLAG_WAIT_FOR_DISCONT
131 132
}
GstOggFlag;
133

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
134 135 136
struct _GstOggDemux
{
  GstElement element;
137

138
  /* pad */
139
  GstFilePad *sinkpad;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
140

141
  /* state */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
142
  GstOggState state;
143 144 145 146 147 148
  GArray *chains;               /* list of chains we know */
  gint current_chain;           /* id of chain that currently "plays" */
  gboolean bos;                 /* no-more-pads signal needs this */
  /* setup */
  GSList *unordered;            /* streams we haven't found chains for yet */
  guint setup_state;            /* seperate from global state */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
149

150
  /* ogg stuff */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
151 152
  ogg_sync_state sync;

153
  /* seeking */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
154
  GstOggPad *seek_pad;
155 156
  gint64 seek_to;
  gint64 seek_skipped;
157
  guint64 seek_offset;
158
  GstFormat seek_format;
159
  gint seek_try;
160 161
};

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
162 163
struct _GstOggDemuxClass
{
164 165 166 167
  GstElementClass parent_class;
};

/* signals and args */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
168 169
enum
{
170 171 172 173
  /* FILL ME */
  LAST_SIGNAL
};

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
174 175
enum
{
176 177
  ARG_0
      /* FILL ME */
178 179
};

David Schleef's avatar
David Schleef committed
180
static GstStaticPadTemplate ogg_demux_src_template_factory =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
181 182 183 184
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);
David Schleef's avatar
David Schleef committed
185 186

static GstStaticPadTemplate ogg_demux_sink_template_factory =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
187 188 189 190 191 192
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/ogg")
    );

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
/* different setup phases */
typedef enum
{
  SETUP_INVALID,
  SETUP_READ_FIRST_BOS,
  SETUP_READ_BOS,
  SETUP_FIND_LAST_CHAIN,
  SETUP_FIND_END_OF_CHAIN,
  SETUP_FIND_END_OF_STREAMS,
  SETUP_FIND_END_OF_LAST_STREAMS
}
GstOggSetupState;

typedef struct
{
  gboolean (*init) (GstOggDemux * ogg);
  gboolean (*process) (GstOggDemux * ogg, ogg_page * page);
}
SetupStateFunc;

static gboolean _read_bos_init (GstOggDemux * ogg);
static gboolean _read_bos_process (GstOggDemux * ogg, ogg_page * page);
static gboolean _find_chain_init (GstOggDemux * ogg);
static gboolean _find_chain_process (GstOggDemux * ogg, ogg_page * page);
static gboolean _find_last_chain_init (GstOggDemux * ogg);
static gboolean _find_last_chain_process (GstOggDemux * ogg, ogg_page * page);
static gboolean _find_streams_init (GstOggDemux * ogg);
static gboolean _find_streams_process (GstOggDemux * ogg, ogg_page * page);

static SetupStateFunc setup_funcs[] = {
  {NULL, NULL},
  {_read_bos_init, _read_bos_process},
  {_read_bos_init, _read_bos_process},
  {_find_last_chain_init, _find_last_chain_process},
  {_find_chain_init, _find_chain_process},
  {_find_streams_init, _find_streams_process},
  {_find_streams_init, _find_streams_process},
  {NULL, NULL}                  /* just because */
};

static gboolean gst_ogg_demux_set_setup_state (GstOggDemux * ogg,
    GstOggSetupState state);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
235 236 237 238 239 240

static void gst_ogg_demux_finalize (GObject * object);

static gboolean gst_ogg_demux_src_event (GstPad * pad, GstEvent * event);
static const GstEventMask *gst_ogg_demux_get_event_masks (GstPad * pad);
static const GstQueryType *gst_ogg_demux_get_query_types (GstPad * pad);
241
static const GstFormat *gst_ogg_demux_get_formats (GstPad * pad);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
242 243 244 245

static gboolean gst_ogg_demux_src_query (GstPad * pad,
    GstQueryType type, GstFormat * format, gint64 * value);

246 247
static void gst_ogg_demux_iterate (GstFilePad * pad);
static gboolean gst_ogg_demux_handle_event (GstPad * pad, GstEvent * event);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
248 249 250 251 252 253 254 255

static GstElementStateReturn gst_ogg_demux_change_state (GstElement * element);

static GstOggPad *gst_ogg_pad_new (GstOggDemux * ogg, int serial_no);
static void gst_ogg_pad_remove (GstOggDemux * ogg, GstOggPad * ogg_pad);
static void gst_ogg_pad_reset (GstOggDemux * ogg, GstOggPad * pad);
static void gst_ogg_demux_push (GstOggDemux * ogg, ogg_page * page);
static void gst_ogg_pad_push (GstOggDemux * ogg, GstOggPad * ogg_pad);
256 257
static void gst_ogg_chains_clear (GstOggDemux * ogg);
static void gst_ogg_add_chain (GstOggDemux * ogg);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
258 259 260 261

static GstCaps *gst_ogg_type_find (ogg_packet * packet);

static void gst_ogg_print (GstOggDemux * demux);
262

263 264 265
#define GST_OGG_SET_STATE(ogg, new_state) G_STMT_START{				\
  GST_DEBUG_OBJECT (ogg, "setting state to %s", G_STRINGIFY (new_state));	\
  ogg->state = new_state;							\
266 267
  ogg->setup_state = (new_state == GST_OGG_STATE_SETUP) ?			\
      SETUP_READ_FIRST_BOS : SETUP_INVALID;	        		\
268
}G_STMT_END
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
269

270
GST_BOILERPLATE (GstOggDemux, gst_ogg_demux, GstElement, GST_TYPE_ELEMENT)
271

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
272
     static void gst_ogg_demux_base_init (gpointer g_class)
273 274
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
275 276 277 278 279
  static GstElementDetails gst_ogg_demux_details =
      GST_ELEMENT_DETAILS ("ogg demuxer",
      "Codec/Demuxer",
      "demux ogg streams (info about ogg: http://xiph.org)",
      "Benjamin Otte <otte@gnome.org>");
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
280

281 282 283
  gst_element_class_set_details (element_class, &gst_ogg_demux_details);

  gst_element_class_add_pad_template (element_class,
David Schleef's avatar
David Schleef committed
284
      gst_static_pad_template_get (&ogg_demux_sink_template_factory));
285
  gst_element_class_add_pad_template (element_class,
David Schleef's avatar
David Schleef committed
286
      gst_static_pad_template_get (&ogg_demux_src_template_factory));
287 288
}
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
289
gst_ogg_demux_class_init (GstOggDemuxClass * klass)
290
{
291 292
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
293

294 295
  gstelement_class->change_state = gst_ogg_demux_change_state;

296
  gobject_class->finalize = gst_ogg_demux_finalize;
297
}
298

299
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
300
gst_ogg_demux_init (GstOggDemux * ogg)
301
{
302 303
  GST_FLAG_SET (ogg, GST_ELEMENT_EVENT_AWARE);

304
  /* create the sink pad */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
305
  ogg->sinkpad =
306 307 308 309
      GST_FILE_PAD (gst_file_pad_new (gst_static_pad_template_get
          (&ogg_demux_sink_template_factory), "sink"));
  gst_file_pad_set_iterate_function (ogg->sinkpad, gst_ogg_demux_iterate);
  gst_file_pad_set_event_function (ogg->sinkpad, gst_ogg_demux_handle_event);
310 311
  gst_pad_set_formats_function (GST_PAD (ogg->sinkpad),
      gst_ogg_demux_get_formats);
312
  gst_element_add_pad (GST_ELEMENT (ogg), GST_PAD (ogg->sinkpad));
313 314

  /* initalize variables */
315 316
  GST_OGG_SET_STATE (ogg, GST_OGG_STATE_START);
  ogg->chains = g_array_new (TRUE, TRUE, sizeof (GstOggChain));
317
  ogg->current_chain = -1;
318
}
319

320
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
321
gst_ogg_demux_finalize (GObject * object)
322 323 324 325 326
{
  GstOggDemux *ogg;

  ogg = GST_OGG_DEMUX (object);

327
  ogg_sync_clear (&ogg->sync);
Johan Dahlin's avatar
Johan Dahlin committed
328

329 330 331 332
  /* chains are removed when going to READY */
  g_assert (ogg->current_chain == -1);
  g_assert (ogg->chains->len == 0);
  g_array_free (ogg->chains, TRUE);
Johan Dahlin's avatar
Johan Dahlin committed
333 334 335

  if (G_OBJECT_CLASS (parent_class)->finalize)
    G_OBJECT_CLASS (parent_class)->finalize (object);
336 337
}

338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
static const GstFormat *
gst_ogg_demux_get_formats (GstPad * pad)
{
  static GstFormat src_formats[] = {
    GST_FORMAT_BYTES,
    GST_FORMAT_DEFAULT,         /* granulepos */
    GST_FORMAT_TIME,
    0
  };
  static GstFormat sink_formats[] = {
    GST_FORMAT_BYTES,
    GST_FORMAT_DEFAULT,         /* bytes */
    0
  };

  return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats);
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
356 357
static const GstEventMask *
gst_ogg_demux_get_event_masks (GstPad * pad)
358 359
{
  static const GstEventMask gst_ogg_demux_src_event_masks[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
360 361
    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH},
    {0,}
362
  };
363

364 365
  return gst_ogg_demux_src_event_masks;
}
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
366 367
static const GstQueryType *
gst_ogg_demux_get_query_types (GstPad * pad)
368 369 370 371 372 373
{
  static const GstQueryType gst_ogg_demux_src_query_types[] = {
    GST_QUERY_TOTAL,
    GST_QUERY_POSITION,
    0
  };
374

375 376 377
  return gst_ogg_demux_src_query_types;
}

378
static GstOggPad *
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
379
gst_ogg_get_pad_by_pad (GstOggDemux * ogg, GstPad * pad)
380 381 382
{
  GSList *walk;
  GstOggPad *cur;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
383

384 385 386 387
  if (ogg->current_chain == -1) {
    GST_DEBUG_OBJECT (ogg, "no active chain, returning NULL");
    return NULL;
  }
388 389 390 391 392 393 394 395
  for (walk = CURRENT_CHAIN (ogg)->pads; walk; walk = g_slist_next (walk)) {
    cur = (GstOggPad *) walk->data;
    if (cur->pad == pad)
      return cur;
  }
  return NULL;
}

396 397 398 399 400 401 402 403 404 405 406 407 408
/* will subtract the base from a given granulepos in a stream
 * (lineairly) and return the relative granulepos from the first
 * packet in the stream, or some approximation thereof. Input is
 * in granulepos units, output is either granulepos or time.
 * Uses time internally. Returns -1 on error.
 */
static gint64
get_relative (GstOggDemux * ogg, GstOggPad * cur, gint64 granpos, GstFormat out)
{
  gint64 time, start = -1, tmp;
  GstFormat fmt;

  /* we're gonna ask our peer */
409
  if (!cur->pad || !GST_PAD_PEER (cur->pad))
410 411 412 413 414 415 416 417 418 419
    return -1;

  /* lineair unit (time) */
  fmt = GST_FORMAT_TIME;
  if (!gst_pad_convert (GST_PAD_PEER (cur->pad),
          GST_FORMAT_DEFAULT, granpos, &fmt, &time))
    return -1;

  /* get base for this chain */
  FOR_PAD_IN_CURRENT_CHAIN (ogg, pad,
420
      if (pad->start != -1 && pad->pad &&
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
          GST_PAD_PEER (pad->pad) &&
          gst_pad_convert (GST_PAD_PEER (pad->pad),
              GST_FORMAT_DEFAULT, pad->start,
              &fmt, &tmp) && (start == -1 || tmp < start))
      start = tmp;);
  if (start == -1)
    return -1;

  /* base is *end of first page*, so subtract $random amount to make
   * us think it's the start of the page (= 1 second) */
  if (start > GST_SECOND)
    start -= GST_SECOND;
  else
    start = 0;

  /* subtract */
  if (time > start)
    time -= start;
  else
    time = 0;

  /* convert back to $outputformat */
  if (!gst_pad_convert (GST_PAD_PEER (cur->pad),
          GST_FORMAT_TIME, time, &out, &tmp))
    return -1;

  return tmp;
}

450 451 452 453
/* the query function on the src pad only knows about granulepos
 * values but we can use the peer plugins to convert the granulepos
 * (which is supposed to be the default format) to any other format 
 */
454
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
455 456
gst_ogg_demux_src_query (GstPad * pad, GstQueryType type,
    GstFormat * format, gint64 * value)
457 458
{
  gboolean res = FALSE;
459 460
  GstOggDemux *ogg;
  GstOggPad *cur;
461
  guint64 granulepos = 0;
462

463 464 465
  ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));

  cur = gst_ogg_get_pad_by_pad (ogg, pad);
466 467 468
  if (!cur)
    return FALSE;

469
  switch (type) {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
470
    case GST_QUERY_TOTAL:{
471
      if (cur->length != 0 && cur->length > cur->start) {
472
        granulepos = cur->length;
473 474
        res = TRUE;
      }
475 476 477
      break;
    }
    case GST_QUERY_POSITION:
478
      if (cur->length != 0 && cur->length > cur->start) {
479
        granulepos = cur->known_offset;
480
        res = TRUE;
481
      }
482 483 484 485
      break;
    default:
      break;
  }
486

487
  if (res) {
488
    gint64 time;
489

490 491 492
    time = get_relative (ogg, cur, granulepos, GST_FORMAT_TIME);
    if (time == -1)
      return FALSE;
493

494 495
    /* still ok, got a granulepos then */
    switch (*format) {
496
      case GST_FORMAT_TIME:
497
        /* fine, result should be granulepos */
498
        *value = time;
499 500 501
        break;
      default:
        /* something we have to ask our peer */
502 503
        if (GST_PAD_PEER (pad)) {
          res = gst_pad_convert (GST_PAD_PEER (pad),
504
              GST_FORMAT_TIME, time, format, value);
505 506
        } else {
          res = FALSE;
507
        }
508 509 510
        break;
    }
  }
511 512 513
  return res;
}

514 515 516 517 518
/* The current seeking implementation is the most simple I could come up with:
 * - when seeking forwards, just discard data until desired position is reached
 * - when seeking backwards, seek to beginning and seek forward from there
 * Anyone is free to improve this algorithm as it is quite stupid and probably
 * really slow.
519 520 521 522 523 524
 *
 * The seeking position can be specified as the granulepos in case a decoder
 * plugin can give us a correct granulepos, or in timestamps.
 * In the case of a time seek, we repeadedly ask the peer element to 
 * convert the granulepos in the page to a timestamp. We go back to playing
 * when the timestamp is the requested one (or close enough to it).
525
 */
526
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
527
gst_ogg_demux_src_event (GstPad * pad, GstEvent * event)
528 529
{
  GstOggDemux *ogg;
530
  GstOggPad *cur;
531 532

  ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
533
  cur = gst_ogg_get_pad_by_pad (ogg, pad);
534

535 536
  /* FIXME: optimize this so events from inactive chains work? 
   * in theory there shouldn't be an exisiting pad for inactive chains */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
537 538 539
  if (cur == NULL)
    goto error;

540 541
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
542
    {
543
      gint64 offset, position, total, seek_offset;
544 545
      GstFormat format, my_format;
      gboolean res;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
546

547
      format = GST_EVENT_SEEK_FORMAT (event);
548
      offset = GST_EVENT_SEEK_OFFSET (event);
549 550 551 552 553 554 555 556 557 558

      my_format = format;

      /* get position, we'll need it later to decide what direction
       * we need to seek in */
      res = gst_ogg_demux_src_query (pad,
          GST_QUERY_POSITION, &my_format, &position);
      if (!res)
        goto error;

559
      switch (GST_EVENT_SEEK_METHOD (event)) {
560
        case GST_SEEK_METHOD_END:
561 562 563 564 565 566 567 568 569 570 571
        {
          gint64 value;

          /* invalid offset */
          if (offset > 0)
            goto error;

          /* calculate total length first */
          res = gst_ogg_demux_src_query (pad,
              GST_QUERY_TOTAL, &my_format, &value);
          if (!res)
572
            goto error;
573 574 575

          /* requested position is end + offset */
          offset = value + offset;
576
          break;
577
        }
578
        case GST_SEEK_METHOD_CUR:
579 580 581
        {
          /* add current position to offset */
          offset = position + offset;
582
          break;
583
        }
584
        case GST_SEEK_METHOD_SET:
585
          /* offset and format are fine here */
586 587 588 589
          break;
        default:
          g_warning ("invalid seek method in seek event");
          goto error;
590
      }
591

592 593
      my_format = GST_FORMAT_TIME;
      if (format != GST_FORMAT_TIME) {
594 595
        if (!GST_PAD_PEER (pad) ||
            !gst_pad_convert (GST_PAD_PEER (pad), format,
596
                offset, &my_format, &position))
597
          goto error;
598 599
      } else {
        position = offset;
600
      }
601 602
      if (!gst_ogg_demux_src_query (pad, GST_QUERY_TOTAL, &my_format, &total))
        goto error;
603 604
      if (position < 0)
        position = 0;
605 606
      else if (position > total)
        position = total;
607 608 609
      seek_offset = gst_file_pad_get_length (ogg->sinkpad) *
          ((gdouble) position) / ((gdouble) total);
      if (gst_file_pad_seek (ogg->sinkpad, seek_offset,
610
              GST_SEEK_METHOD_SET) != 0)
611
        goto error;
612
      ogg->seek_try = 1;
613 614
      ogg_sync_clear (&ogg->sync);

615
      GST_OGG_SET_STATE (ogg, GST_OGG_STATE_SEEK);
616
      FOR_PAD_IN_CURRENT_CHAIN (ogg, pad,
617
          pad->flags |= GST_OGG_PAD_NEEDS_DISCONT;);
618 619
      if (GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_FLUSH) {
        FOR_PAD_IN_CURRENT_CHAIN (ogg, pad,
620
            pad->flags |= GST_OGG_PAD_NEEDS_FLUSH;);
621
      }
622 623
      GST_DEBUG_OBJECT (ogg,
          "initiating seeking to format %d, offset %" G_GUINT64_FORMAT, format,
624
          offset);
625 626

      /* store format and position we seek to */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
627
      ogg->seek_pad = cur;
628 629 630
      ogg->seek_to = position;
      ogg->seek_format = GST_FORMAT_TIME;
      ogg->seek_offset = seek_offset;
631

632 633 634
      gst_event_unref (event);
      return TRUE;
    }
635
    default:
636
      return gst_pad_event_default (pad, event);
637 638
  }

639
  g_assert_not_reached ();
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
640

641
error:
642 643 644
  gst_event_unref (event);
  return FALSE;
}
645

646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
static gboolean
gst_ogg_demux_src_convert (GstPad * pad,
    GstFormat src_format, gint64 src_value,
    GstFormat * dest_format, gint64 * dest_value)
{
  gboolean res = FALSE;
  GstOggDemux *ogg;
  GstOggPad *cur;

  ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
  cur = gst_ogg_get_pad_by_pad (ogg, pad);

  /* fill me, not sure with what... */

  return res;
}

663
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
664
gst_ogg_start_playing (GstOggDemux * ogg)
665
{
666 667
  GST_DEBUG_OBJECT (ogg, "done with setup, changing to playback now");
  if (gst_file_pad_seek (ogg->sinkpad, 0, GST_SEEK_METHOD_SET) != 0) {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
668
    GST_ELEMENT_ERROR (ogg, CORE, SEEK, (NULL),
669
        ("cannot seek to start after EOS"));
670
  }
671 672 673 674 675 676
  ogg_sync_clear (&ogg->sync);
  if (ogg->current_chain >= 0) {
    ogg->current_chain = 0;
  } else {
    gst_ogg_add_chain (ogg);
  }
677 678 679 680 681 682
  GST_FLAG_UNSET (ogg, GST_OGG_FLAG_EOS);
  GST_FLAG_SET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT);
  GST_OGG_SET_STATE (ogg, GST_OGG_STATE_PLAY);
  gst_ogg_print (ogg);
}

683
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
684
gst_ogg_demux_handle_event (GstPad * pad, GstEvent * event)
685 686
{
  GstOggDemux *ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
687

688 689
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_DISCONTINUOUS:
690 691 692 693
      GST_DEBUG_OBJECT (ogg, "got a discont event");
      ogg_sync_reset (&ogg->sync);
      gst_event_unref (event);
      GST_FLAG_UNSET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT);
694
      FOR_PAD_IN_CURRENT_CHAIN (ogg, pad,
695
          pad->flags |= GST_OGG_PAD_NEEDS_DISCONT;);
696
      break;
697 698 699 700 701 702
    default:
      gst_pad_event_default (pad, event);
      break;
  }
  return TRUE;
}
703

704 705 706 707 708 709
static void
gst_ogg_demux_eos (GstOggDemux * ogg)
{
  guint i;
  GSList *walk;
  GstEvent *event;
710

711 712 713 714 715 716 717 718 719
  GST_DEBUG_OBJECT (ogg, "got EOS");
  ogg->current_chain = -1;
  if (ogg->state == GST_OGG_STATE_SETUP) {
    gst_ogg_start_playing (ogg);
    return;
  }
  event = gst_event_new (GST_EVENT_EOS);
  for (i = 0; i < ogg->chains->len; i++) {
    GstOggChain *chain = &g_array_index (ogg->chains, GstOggChain, i);
720

721 722 723 724 725 726
    for (walk = chain->pads; walk; walk = g_slist_next (walk)) {
      GstOggPad *pad = (GstOggPad *) walk->data;

      if (pad->pad && GST_PAD_IS_USABLE (pad->pad)) {
        gst_data_ref (GST_DATA (event));
        gst_pad_push (pad->pad, GST_DATA (event));
727
      }
728
    }
729
  }
730 731 732 733 734 735 736 737 738 739
  gst_element_set_eos (GST_ELEMENT (ogg));
  gst_event_unref (event);
}

static GstOggPad *
gst_ogg_pad_get_in_chain (GstOggDemux * ogg, guint chain, int serial)
{
  FOR_PAD_IN_CHAIN (ogg, pad, chain, if (pad->serial == serial)
      return pad;);
  return NULL;
740
}
741 742 743

/* get the pad with the given serial in the current stream or NULL if none */
static GstOggPad *
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
744
gst_ogg_pad_get_in_current_chain (GstOggDemux * ogg, int serial)
745
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
746 747
  if (ogg->current_chain == -1)
    return NULL;
748
  g_return_val_if_fail (ogg->current_chain < ogg->chains->len, NULL);
749 750
  return gst_ogg_pad_get_in_chain (ogg, ogg->current_chain, serial);
}
751

752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
/* FIXME: HACK - i dunno if this is supported ogg API */
static guint
gst_ogg_page_get_length (ogg_page * page)
{
  return page->header_len + page->body_len;
}

static gint64
gst_ogg_demux_position (GstOggDemux * ogg)
{
  gint64 pos = gst_file_pad_tell (ogg->sinkpad);

  if (pos < 0)
    return pos;

  return pos - ogg->sync.fill + ogg->sync.returned;
}

/* END HACK */

/* fill in values from this page */
#include <signal.h>
static void
gst_ogg_pad_populate (GstOggDemux * ogg, GstOggPad * pad, ogg_page * page)
{
777
  gint64 start, end, granpos = ogg_page_granulepos (page);
778

779 780 781 782
  if (pad->start > granpos && granpos > 0)
    pad->start = granpos;
  if (pad->length < granpos && granpos > 0)
    pad->length = granpos;
783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
  if (pad->pages < ogg_page_pageno (page))
    pad->pages = ogg_page_pageno (page);
  end = gst_ogg_demux_position (ogg);
  if (end >= 0) {
    /* we need to know the offsets into the stream for the current page */
    start = end - gst_ogg_page_get_length (page);
    //g_print ("really setting start from %lld to %lld\n", pad->start_offset, start);
    //g_print ("really setting end from %lld to %lld\n", pad->end_offset, end);
    if (start < pad->start_offset || pad->start_offset < 0)
      pad->start_offset = start;
    if (ogg_page_bos (page))
      pad->start_found = TRUE;
    if (end > pad->end_offset)
      pad->end_offset = end;
    if (ogg_page_eos (page))
      pad->end_found = TRUE;
  }
}

/* get the ogg pad with the given serial in the unordered list or create and add it */
static GstOggPad *
gst_ogg_pad_get_unordered (GstOggDemux * ogg, ogg_page * page)
{
  GSList *walk;
  GstOggPad *pad;
  int serial = ogg_page_serialno (page);

  for (walk = ogg->unordered; walk; walk = g_slist_next (walk)) {
    pad = (GstOggPad *) walk->data;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
812

813
    if (pad->serial == serial)
814
      goto out;
815
  }
816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836
  pad = gst_ogg_pad_new (ogg, serial);
  ogg->unordered = g_slist_prepend (ogg->unordered, pad);

out:
  /* update start and end pointer if applicable */
  gst_ogg_pad_populate (ogg, pad, page);

  return pad;
}

static GstOggPad *
gst_ogg_pad_get (GstOggDemux * ogg, ogg_page * page)
{
  GstOggPad *pad =
      gst_ogg_pad_get_in_current_chain (ogg, ogg_page_serialno (page));
  if (pad) {
    gst_ogg_pad_populate (ogg, pad, page);
  } else {
    pad = gst_ogg_pad_get_unordered (ogg, page);
  }
  return pad;
837 838 839
}

static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
840
gst_ogg_add_chain (GstOggDemux * ogg)
841 842 843 844 845 846
{
  GST_LOG_OBJECT (ogg, "adding chain %u", ogg->chains->len);
  ogg->current_chain = ogg->chains->len;
  g_array_set_size (ogg->chains, ogg->chains->len + 1);
}

847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
/* abort setup phase and just start playing */
static void
abort_setup (GstOggDemux * ogg)
{
  gst_ogg_print (ogg);
  gst_ogg_chains_clear (ogg);
  gst_ogg_start_playing (ogg);
}

#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT gst_ogg_demux_setup_debug
static gboolean
gst_ogg_demux_set_setup_state (GstOggDemux * ogg, GstOggSetupState state)
{
  g_assert (ogg->state == GST_OGG_STATE_SETUP);
  g_assert (state > 0);
  g_assert (state < G_N_ELEMENTS (setup_funcs));
  g_assert (state != ogg->setup_state);

  GST_DEBUG_OBJECT (ogg, "setting setup state from %d to %d", ogg->setup_state,
      state);
  ogg->setup_state = state;
  if (!setup_funcs[state].init (ogg)) {
    abort_setup (ogg);
    return FALSE;
  }

  return TRUE;
}

/* seeks to the given position if TRUE is returned. Seeks a bit before this
 * offset for syncing. You can call this function multiple times, if sync 
 * failed, it will then seek further back. It will never seek further back as
 * min_offset though.
 */
static gboolean
gst_ogg_demux_seek_before (GstOggDemux * ogg, gint64 offset, gint64 min_offset)
{
  gint64 before;
886 887
  GstOggChain *chain;
  gint streams;
888

889 890 891 892 893 894 895 896 897 898
  /* figure out how many streams are in this chain */
  chain = CURRENT_CHAIN (ogg);
  if (chain) {
    streams = g_slist_length (chain->pads);
  } else {
    streams = 1;
  }

  /* need to multiply the expected page size with the numer of streams we
   * detected to have a good chance of finding all pages */
899
  before = ogg->seek_skipped ? ogg->seek_skipped * SETUP_SEEK_MULTIPLIER :
900 901
      SETUP_EXPECTED_PAGE_SIZE * streams;

902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941
  GST_DEBUG_OBJECT (ogg,
      "seeking to %" G_GINT64_FORMAT " bytes before %" G_GINT64_FORMAT,
      before, offset);
  /* tried to seek to start once, don't try again */
  if (min_offset + ogg->seek_skipped > offset)
    return FALSE;
  if (gst_file_pad_seek (ogg->sinkpad, MAX (min_offset, offset - before),
          GST_SEEK_METHOD_SET) != 0)
    return FALSE;
  ogg_sync_clear (&ogg->sync);
  ogg->seek_skipped = before;
  ogg->seek_to = offset;

  return TRUE;
}

static gboolean
_read_bos_init (GstOggDemux * ogg)
{
  gst_ogg_add_chain (ogg);

  return TRUE;
}

static gboolean
_read_bos_process (GstOggDemux * ogg, ogg_page * page)
{
  /* here we're reading in the bos pages of the current chain */
  if (ogg_page_bos (page)) {
    GstOggPad *pad;

    GST_LOG_OBJECT (ogg,
        "SETUP_READ_BOS: bos found with serial %d, adding to current chain",
        ogg_page_serialno (page));
    pad = gst_ogg_pad_get_unordered (ogg, page);
    ogg->unordered = g_slist_remove (ogg->unordered, pad);
    g_assert (CURRENT_CHAIN (ogg));
    CURRENT_CHAIN (ogg)->pads =
        g_slist_prepend (CURRENT_CHAIN (ogg)->pads, pad);
  } else {
942 943
    gboolean have_all_first_pages = TRUE;

944 945 946 947
    if (CURRENT_CHAIN (ogg)->pads == NULL) {
      GST_ERROR_OBJECT (ogg, "broken ogg stream, chain has no BOS pages");
      return FALSE;
    }
948 949 950 951 952 953 954 955 956 957 958 959 960 961 962

    FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, if (pad->start == (guint64) - 1)
        have_all_first_pages = FALSE;);

    if (have_all_first_pages) {
      GST_DEBUG_OBJECT (ogg,
          "SETUP_READ_BOS: no more bos pages, going to find end of stream");
      if (ogg->setup_state == SETUP_READ_FIRST_BOS) {
        return gst_ogg_demux_set_setup_state (ogg, SETUP_FIND_LAST_CHAIN);
      } else if (ogg->unordered) {
        return gst_ogg_demux_set_setup_state (ogg,
            SETUP_FIND_END_OF_LAST_STREAMS);
      } else {
        return gst_ogg_demux_set_setup_state (ogg, SETUP_FIND_END_OF_STREAMS);
      }
963
    } else {
964 965 966 967
      GstOggPad *pad =
          gst_ogg_pad_get_in_current_chain (ogg, ogg_page_serialno (page));

      gst_ogg_pad_populate (ogg, pad, page);
968 969 970 971 972 973 974 975 976 977 978 979
    }
  }
  return TRUE;
}

static gboolean
_find_chain_get_unknown_part (GstOggDemux * ogg, gint64 * start, gint64 * end)
{
  *start = 0;
  *end = G_MAXINT64;

  g_assert (ogg->current_chain >= 0);
980
  FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, *start = MAX (*start, pad->end_offset););
981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072

  if (ogg->setup_state == SETUP_FIND_LAST_CHAIN) {
    *end = gst_file_pad_get_length (ogg->sinkpad);
    if (*end < 0)
      return FALSE;
  } else {
    GSList *walk;

    g_assert (ogg->unordered != NULL);
    for (walk = ogg->unordered; walk; walk = g_slist_next (walk)) {
      GstOggPad *temp = walk->data;

      *end = MIN (*end, temp->start_offset);
    }
  }
  GST_DEBUG_OBJECT (ogg, "we're looking for a new chain in the range [%"
      G_GINT64_FORMAT ", %" G_GINT64_FORMAT "]", *start, *end);

  /* overlapping chains?! */
  if (*end < *start) {
    GST_ERROR_OBJECT (ogg, "chained streams overlap, bailing out");
    return FALSE;
  }

  return TRUE;
}

static gboolean
_find_last_chain_init (GstOggDemux * ogg)
{
  gint64 end = gst_file_pad_get_length (ogg->sinkpad);

  ogg->seek_skipped = 0;
  if (end < 0)
    return FALSE;
  if (!gst_ogg_demux_seek_before (ogg, end, 0))
    return FALSE;
  return TRUE;
}

static gboolean
_find_last_chain_process (GstOggDemux * ogg, ogg_page * page)
{
  GstOggPad *pad = gst_ogg_pad_get (ogg, page);

  /* optimization: set eos as found - we're investigating last pages here anyway */
  pad->end_found = TRUE;
  /* set to 0 to indicate we found a page */
  ogg->seek_skipped = 0;
  return TRUE;
}

static gboolean
_find_chain_seek (GstOggDemux * ogg, gint64 start, gint64 end)
{
  if (end - start < SETUP_PASSTHROUGH_SIZE) {
    GST_LOG_OBJECT (ogg,
        "iterating through remaining window, because it's smaller than %u bytes",
        SETUP_PASSTHROUGH_SIZE);
    if (ogg->seek_to >= start) {
      ogg->seek_skipped = 0;
      if (!gst_ogg_demux_seek_before (ogg, start, start))
        return FALSE;
    }
  } else {
    if (!gst_ogg_demux_seek_before (ogg, (start + end) / 2, start))
      return FALSE;
  }
  return TRUE;
}

static gboolean
_find_chain_init (GstOggDemux * ogg)
{
  gint64 start, end;

  ogg->seek_skipped = 0;
  ogg->seek_to = -1;
  if (!_find_chain_get_unknown_part (ogg, &start, &end))
    return FALSE;
  if (!_find_chain_seek (ogg, start, end))
    return FALSE;
  return TRUE;
}

static gboolean
_find_chain_process (GstOggDemux * ogg, ogg_page * page)
{
  gint64 start, end;

  if (!_find_chain_get_unknown_part (ogg, &start, &end))
    return FALSE;
1073

1074 1075 1076 1077 1078
  if (ogg->seek_to <= start && gst_ogg_demux_position (ogg) > end) {
    /* we now should have the first bos page, because
     * - we seeked to a point in the known chain
     * - we're now in a part that belongs to the unordered streams
     */