gstoggdemux.c 92.8 KB
Newer Older
1
/* GStreamer
2
 * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
3
 *
4
 * gstoggdemux.c: ogg stream demuxer
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 * 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.
 */

Wim Taymans's avatar
Wim Taymans committed
22
23
/**
 * SECTION:element-oggdemux
24
 * @see_also: <link linkend="gst-plugins-base-plugins-oggmux">oggmux</link>
Wim Taymans's avatar
Wim Taymans committed
25
26
 *
 * This element demuxes ogg files into their encoded audio and video components.
27
28
 *
 * <refsect2>
Wim Taymans's avatar
Wim Taymans committed
29
 * <title>Example pipelines</title>
30
 * |[
Wim Taymans's avatar
Wim Taymans committed
31
 * gst-launch -v filesrc location=test.ogg ! oggdemux ! vorbisdec ! audioconvert ! alsasink
32
 * ]| Decodes the vorbis audio stored inside an ogg container.
Wim Taymans's avatar
Wim Taymans committed
33
34
35
36
37
38
 * </refsect2>
 *
 * Last reviewed on 2006-12-30 (0.10.5)
 */


39
40
41
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
42
#include <string.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
43
#include <gst/gst-i18n-plugin.h>
44
#include <gst/tag/tag.h>
45

Wim Taymans's avatar
Wim Taymans committed
46
47
#include "gstoggdemux.h"

Stefan Kost's avatar
Stefan Kost committed
48
static const GstElementDetails gst_ogg_demux_details =
j^'s avatar
j^ committed
49
GST_ELEMENT_DETAILS ("Ogg demuxer",
50
51
    "Codec/Demuxer",
    "demux ogg streams (info about ogg: http://xiph.org)",
52
    "Wim Taymans <wim@fluendo.com>");
53

54
#define CHUNKSIZE (8500)        /* this is out of vorbisfile */
55
56
#define SKELETON_FISHEAD_SIZE 64
#define SKELETON_FISBONE_MIN_SIZE 52
57

58
#define GST_FLOW_LIMIT GST_FLOW_CUSTOM_ERROR
59

60
61
62
#define GST_CHAIN_LOCK(ogg)     g_mutex_lock((ogg)->chain_lock)
#define GST_CHAIN_UNLOCK(ogg)   g_mutex_unlock((ogg)->chain_lock)

David Schleef's avatar
David Schleef committed
63
64
GST_DEBUG_CATEGORY (gst_ogg_demux_debug);
GST_DEBUG_CATEGORY (gst_ogg_demux_setup_debug);
65
66
#define GST_CAT_DEFAULT gst_ogg_demux_debug

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

static ogg_packet *
_ogg_packet_copy (const ogg_packet * packet)
{
  ogg_packet *ret = g_new0 (ogg_packet, 1);

  *ret = *packet;
  ret->packet = g_memdup (packet->packet, packet->bytes);

  return ret;
}

static void
_ogg_packet_free (ogg_packet * packet)
{
  g_free (packet->packet);
  g_free (packet);
}

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
static ogg_page *
gst_ogg_page_copy (ogg_page * page)
{
  ogg_page *p = g_new0 (ogg_page, 1);

  /* make a copy of the page */
  p->header = g_memdup (page->header, page->header_len);
  p->header_len = page->header_len;
  p->body = g_memdup (page->body, page->body_len);
  p->body_len = page->body_len;

  return p;
}

static void
gst_ogg_page_free (ogg_page * page)
{
  g_free (page->header);
  g_free (page->body);
  g_free (page);
}

108
109
110
111
static gboolean gst_ogg_demux_collect_chain_info (GstOggDemux * ogg,
    GstOggChain * chain);
static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg,
    GstOggChain * chain, GstEvent * event);
112
static void gst_ogg_chain_mark_discont (GstOggChain * chain);
113

114
115
static gboolean gst_ogg_demux_perform_seek (GstOggDemux * ogg,
    GstEvent * event);
116
117
static gboolean gst_ogg_demux_receive_event (GstElement * element,
    GstEvent * event);
118

119
120
121
122
static void gst_ogg_pad_class_init (GstOggPadClass * klass);
static void gst_ogg_pad_init (GstOggPad * pad);
static void gst_ogg_pad_dispose (GObject * object);
static void gst_ogg_pad_finalize (GObject * object);
Wim Taymans's avatar
Wim Taymans committed
123

124
static const GstQueryType *gst_ogg_pad_query_types (GstPad * pad);
Wim Taymans's avatar
Wim Taymans committed
125
static gboolean gst_ogg_pad_src_query (GstPad * pad, GstQuery * query);
126
127
static gboolean gst_ogg_pad_event (GstPad * pad, GstEvent * event);
static GstCaps *gst_ogg_pad_getcaps (GstPad * pad);
128
129
130
static GstOggPad *gst_ogg_chain_get_stream (GstOggChain * chain,
    glong serialno);

131
132
static GstFlowReturn gst_ogg_demux_combine_flows (GstOggDemux * ogg,
    GstOggPad * pad, GstFlowReturn ret);
133
static void gst_ogg_demux_sync_streams (GstOggDemux * ogg);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
134

135
G_DEFINE_TYPE (GstOggPad, gst_ogg_pad, GST_TYPE_PAD);
136

137
138
static void
gst_ogg_pad_class_init (GstOggPadClass * klass)
139
{
140
  GObjectClass *gobject_class;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
141

142
  gobject_class = (GObjectClass *) klass;
143

144
145
146
  gobject_class->dispose = gst_ogg_pad_dispose;
  gobject_class->finalize = gst_ogg_pad_finalize;
}
147

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
static void
gst_ogg_pad_init (GstOggPad * pad)
{
  gst_pad_set_event_function (GST_PAD (pad),
      GST_DEBUG_FUNCPTR (gst_ogg_pad_event));
  gst_pad_set_getcaps_function (GST_PAD (pad),
      GST_DEBUG_FUNCPTR (gst_ogg_pad_getcaps));
  gst_pad_set_query_type_function (GST_PAD (pad),
      GST_DEBUG_FUNCPTR (gst_ogg_pad_query_types));
  gst_pad_set_query_function (GST_PAD (pad),
      GST_DEBUG_FUNCPTR (gst_ogg_pad_src_query));

  pad->mode = GST_OGG_PAD_MODE_INIT;

  pad->current_granule = -1;
163
  pad->keyframe_granule = -1;
164

165
  pad->start_time = GST_CLOCK_TIME_NONE;
166

167
168
  pad->last_stop = GST_CLOCK_TIME_NONE;

169
  pad->have_type = FALSE;
170
  pad->continued = NULL;
David Schleef's avatar
David Schleef committed
171
  pad->map.headers = NULL;
172
  pad->map.queued = NULL;
173
}
174

175
static void
176
gst_ogg_pad_dispose (GObject * object)
177
{
178
179
180
181
182
  GstOggPad *pad = GST_OGG_PAD (object);

  pad->chain = NULL;
  pad->ogg = NULL;

183
  g_list_foreach (pad->map.headers, (GFunc) _ogg_packet_free, NULL);
David Schleef's avatar
David Schleef committed
184
185
  g_list_free (pad->map.headers);
  pad->map.headers = NULL;
186
  g_list_foreach (pad->map.queued, (GFunc) _ogg_packet_free, NULL);
187
188
  g_list_free (pad->map.queued);
  pad->map.queued = NULL;
189

190
191
192
193
194
  /* clear continued pages */
  g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL);
  g_list_free (pad->continued);
  pad->continued = NULL;

David Schleef's avatar
David Schleef committed
195
  ogg_stream_reset (&pad->map.stream);
196

197
  G_OBJECT_CLASS (gst_ogg_pad_parent_class)->dispose (object);
198
}
199

200
static void
201
gst_ogg_pad_finalize (GObject * object)
202
{
203
  GstOggPad *pad = GST_OGG_PAD (object);
204

David Schleef's avatar
David Schleef committed
205
  ogg_stream_clear (&pad->map.stream);
Johan Dahlin's avatar
Johan Dahlin committed
206

207
  G_OBJECT_CLASS (gst_ogg_pad_parent_class)->finalize (object);
208
209
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
210
static const GstQueryType *
211
gst_ogg_pad_query_types (GstPad * pad)
212
{
213
  static const GstQueryType query_types[] = {
214
    GST_QUERY_DURATION,
Wim Taymans's avatar
Wim Taymans committed
215
    GST_QUERY_SEEKING,
216
217
    0
  };
218

219
  return query_types;
220
221
}

222
223
static GstCaps *
gst_ogg_pad_getcaps (GstPad * pad)
224
{
225
  return gst_caps_ref (GST_PAD_CAPS (pad));
226
227
}

228
static gboolean
Wim Taymans's avatar
Wim Taymans committed
229
gst_ogg_pad_src_query (GstPad * pad, GstQuery * query)
230
{
231
  gboolean res = TRUE;
232
233
  GstOggDemux *ogg;

234
  ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
235

Wim Taymans's avatar
Wim Taymans committed
236
  switch (GST_QUERY_TYPE (query)) {
Wim Taymans's avatar
Wim Taymans committed
237
    case GST_QUERY_DURATION:
238
239
    {
      GstFormat format;
240
      gint64 total_time;
241

Wim Taymans's avatar
Wim Taymans committed
242
      gst_query_parse_duration (query, &format, NULL);
243
      /* can only get position in time */
244
245
246
      if (format != GST_FORMAT_TIME)
        goto wrong_format;

Wim Taymans's avatar
Wim Taymans committed
247
248
      if (ogg->pullmode) {
        /* we must return the total length */
249
250
        total_time = ogg->total_time;
      } else {
Wim Taymans's avatar
Wim Taymans committed
251
        /* in push mode we can answer the query and we must return -1 */
252
253
254
255
        total_time = -1;
      }

      gst_query_set_duration (query, GST_FORMAT_TIME, total_time);
256
      break;
257
    }
258
259
260
261
262
263
    case GST_QUERY_SEEKING:
    {
      GstFormat format;

      gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
      if (format == GST_FORMAT_TIME) {
Wim Taymans's avatar
Wim Taymans committed
264
        gst_query_set_seeking (query, GST_FORMAT_TIME, ogg->pullmode,
265
266
267
268
269
270
271
            0, ogg->total_time);
      } else {
        res = FALSE;
      }
      break;
    }

272
    default:
273
      res = gst_pad_query_default (pad, query);
274
275
      break;
  }
276
done:
277
278
  gst_object_unref (ogg);

279
  return res;
280
281
282
283
284
285
286
287

  /* ERRORS */
wrong_format:
  {
    GST_DEBUG_OBJECT (ogg, "only query duration on TIME is supported");
    res = FALSE;
    goto done;
  }
288
289
}

290
291
292
293
294
295
296
297
298
299
static gboolean
gst_ogg_demux_receive_event (GstElement * element, GstEvent * event)
{
  gboolean res;
  GstOggDemux *ogg;

  ogg = GST_OGG_DEMUX (element);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
Wim Taymans's avatar
Wim Taymans committed
300
      /* can't seek if we are not pullmode, FIXME could pass the
301
302
       * seek query upstream after converting it to bytes using
       * the average bitrate of the stream. */
Wim Taymans's avatar
Wim Taymans committed
303
304
      if (!ogg->pullmode) {
        GST_DEBUG_OBJECT (ogg, "seek on pull mode stream not implemented yet");
305
306
307
308
        goto error;
      }

      /* now do the seek */
309
      res = gst_ogg_demux_perform_seek (ogg, event);
310
      gst_event_unref (event);
311
312
      break;
    default:
313
      GST_DEBUG_OBJECT (ogg, "We only handle seek events here");
314
315
316
317
318
      goto error;
  }

  return res;

319
  /* ERRORS */
320
error:
321
322
323
324
325
  {
    GST_DEBUG_OBJECT (ogg, "error handling event");
    gst_event_unref (event);
    return FALSE;
  }
326
327
}

328
static gboolean
329
gst_ogg_pad_event (GstPad * pad, GstEvent * event)
330
{
331
  gboolean res;
332
333
  GstOggDemux *ogg;

334
  ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
335

336
337
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
Wim Taymans's avatar
Wim Taymans committed
338
      /* can't seek if we are not pullmode, FIXME could pass the
339
340
       * seek query upstream after converting it to bytes using
       * the average bitrate of the stream. */
Wim Taymans's avatar
Wim Taymans committed
341
342
      if (!ogg->pullmode) {
        GST_DEBUG_OBJECT (ogg, "seek on pull mode stream not implemented yet");
343
        goto error;
344
      }
345

346
      /* now do the seek */
347
      res = gst_ogg_demux_perform_seek (ogg, event);
348
      gst_event_unref (event);
349
      break;
350
    default:
351
352
      res = gst_pad_event_default (pad, event);
      break;
353
  }
354
355
356
done:
  gst_object_unref (ogg);

357
  return res;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
358

359
  /* ERRORS */
360
error:
361
  {
362
    GST_DEBUG_OBJECT (ogg, "error handling event");
363
    gst_event_unref (event);
364
365
    res = FALSE;
    goto done;
366
  }
367
368
}

369
static void
370
gst_ogg_pad_reset (GstOggPad * pad)
371
{
David Schleef's avatar
David Schleef committed
372
  ogg_stream_reset (&pad->map.stream);
373

374
375
  GST_DEBUG_OBJECT (pad, "doing reset");

376
377
378
379
380
381
  /* clear continued pages */
  g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL);
  g_list_free (pad->continued);
  pad->continued = NULL;

  pad->last_ret = GST_FLOW_OK;
382
  pad->last_stop = GST_CLOCK_TIME_NONE;
383
  pad->current_granule = -1;
384
  pad->keyframe_granule = -1;
385
386
}

387
388
389
390
391
392
393
394
395
396
397
398
/* called when the skeleton fishead is found. Caller ensures the packet is
 * precisely the correct size; we don't re-check this here. */
static void
gst_ogg_pad_parse_skeleton_fishead (GstOggPad * pad, ogg_packet * packet)
{
  GstOggDemux *ogg = pad->ogg;
  guint8 *data = packet->packet;
  guint16 major, minor;
  gint64 prestime_n, prestime_d;
  gint64 basetime_n, basetime_d;

  /* skip "fishead\0" */
David Schleef's avatar
David Schleef committed
399
400
401
402
403
404
  major = GST_READ_UINT16_LE (data + 8);
  minor = GST_READ_UINT16_LE (data + 10);
  prestime_n = (gint64) GST_READ_UINT64_LE (data + 12);
  prestime_d = (gint64) GST_READ_UINT64_LE (data + 20);
  basetime_n = (gint64) GST_READ_UINT64_LE (data + 28);
  basetime_d = (gint64) GST_READ_UINT64_LE (data + 36);
405
406

  ogg->basetime = gst_util_uint64_scale (GST_SECOND, basetime_n, basetime_d);
407
  ogg->prestime = gst_util_uint64_scale (GST_SECOND, prestime_n, prestime_d);
408
  ogg->have_fishead = TRUE;
David Schleef's avatar
David Schleef committed
409
  pad->map.is_skeleton = TRUE;
410
411
  pad->start_time = GST_CLOCK_TIME_NONE;
  GST_INFO_OBJECT (ogg, "skeleton fishead parsed (basetime: %"
412
413
      GST_TIME_FORMAT ", prestime: %" GST_TIME_FORMAT ")",
      GST_TIME_ARGS (ogg->basetime), GST_TIME_ARGS (ogg->prestime));
414
415
416
417
418
419
420
421
422
423
424
425
}

/* function called when a skeleton fisbone is found. Caller ensures that
 * the packet length is sufficient */
static void
gst_ogg_pad_parse_skeleton_fisbone (GstOggPad * pad, ogg_packet * packet)
{
  GstOggPad *fisbone_pad;
  gint64 start_granule;
  guint32 serialno;
  guint8 *data = packet->packet;

David Schleef's avatar
David Schleef committed
426
  serialno = GST_READ_UINT32_LE (data + 12);
427
428
429

  fisbone_pad = gst_ogg_chain_get_stream (pad->chain, serialno);
  if (fisbone_pad) {
David Schleef's avatar
David Schleef committed
430
    if (fisbone_pad->map.have_fisbone)
431
432
433
      /* already parsed */
      return;

David Schleef's avatar
David Schleef committed
434
435
436
437
438
439
440
    fisbone_pad->map.have_fisbone = TRUE;

    fisbone_pad->map.granulerate_n = GST_READ_UINT64_LE (data + 20);
    fisbone_pad->map.granulerate_d = GST_READ_UINT64_LE (data + 28);
    start_granule = GST_READ_UINT64_LE (data + 36);
    fisbone_pad->map.preroll = GST_READ_UINT32_LE (data + 44);
    fisbone_pad->map.granuleshift = GST_READ_UINT8 (data + 48);
441
442

    GST_INFO_OBJECT (pad->ogg, "skeleton fisbone parsed "
443
        "(serialno: %08x start time: %" GST_TIME_FORMAT
David Schleef's avatar
David Schleef committed
444
        " granulerate_n: %d granulerate_d: %d "
445
446
        " preroll: %" G_GUINT32_FORMAT " granuleshift: %d)",
        serialno, GST_TIME_ARGS (fisbone_pad->start_time),
David Schleef's avatar
David Schleef committed
447
448
        fisbone_pad->map.granulerate_n, fisbone_pad->map.granulerate_d,
        fisbone_pad->map.preroll, fisbone_pad->map.granuleshift);
449
450
451
452
453
454
455
  } else {
    GST_WARNING_OBJECT (pad->ogg,
        "found skeleton fisbone for an unknown stream %" G_GUINT32_FORMAT,
        serialno);
  }
}

Wim Taymans's avatar
Wim Taymans committed
456
/* queue data, basically takes the packet, puts it in a buffer and store the
457
 * buffer in the queued list.  */
458
459
460
static GstFlowReturn
gst_ogg_demux_queue_data (GstOggPad * pad, ogg_packet * packet)
{
461
#ifndef GST_DISABLE_GST_DEBUG
462
  GstOggDemux *ogg = pad->ogg;
463
#endif
464

David Schleef's avatar
David Schleef committed
465
466
  GST_DEBUG_OBJECT (ogg, "%p queueing data serial %08x", pad,
      pad->map.serialno);
467

468
  pad->map.queued = g_list_append (pad->map.queued, _ogg_packet_copy (packet));
469
470
471
472
473
474

  /* we are ok now */
  return GST_FLOW_OK;
}

static GstFlowReturn
475
476
gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet,
    gboolean push_headers)
477
478
{
  GstBuffer *buf;
479
  GstFlowReturn ret, cret;
480
  GstOggDemux *ogg = pad->ogg;
481
482
  gint64 current_time;
  GstOggChain *chain;
483
  gint64 duration;
484
  gint offset;
485
  gint trim;
486
487
  GstClockTime out_timestamp, out_duration;
  guint64 out_offset, out_offset_end;
488
  gboolean delta_unit = FALSE;
489
490

  GST_DEBUG_OBJECT (ogg,
David Schleef's avatar
David Schleef committed
491
      "%p streaming to peer serial %08x", pad, pad->map.serialno);
492

493
494
  if (pad->map.is_ogm) {
    const guint8 *data;
495
    long bytes;
496
497

    data = packet->packet;
498
499
500
501
    bytes = packet->bytes;

    if (bytes < 1)
      goto empty_packet;
502
503
504
505

    if (data[0] & 1) {
      /* We don't push header packets for OGM */
      cret = gst_ogg_demux_combine_flows (ogg, pad, GST_FLOW_OK);
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
      goto done;
    } else if (data[0] & 3 && pad->map.is_ogm_text) {
      GstTagList *tags;

      /* We don't push comment packets either for text streams,
       * other streams will handle the comment packets in the
       * decoder */
      buf = gst_buffer_new ();

      GST_BUFFER_DATA (buf) = (guint8 *) data;
      GST_BUFFER_SIZE (buf) = bytes;

      tags = gst_tag_list_from_vorbiscomment_buffer (buf,
          (guint8 *) "\003vorbis", 7, NULL);
      gst_buffer_unref (buf);
      buf = NULL;

      if (tags) {
        GST_DEBUG_OBJECT (ogg, "tags = %" GST_PTR_FORMAT, tags);
        gst_element_found_tags_for_pad (GST_ELEMENT (ogg), GST_PAD_CAST (pad),
            tags);
      } else {
        GST_DEBUG_OBJECT (ogg, "failed to extract tags from vorbis comment");
      }

      cret = gst_ogg_demux_combine_flows (ogg, pad, GST_FLOW_OK);
532
533
534
535
      goto done;
    }

    offset = 1 + (((data[0] & 0xc0) >> 6) | ((data[0] & 0x02) << 1));
536
    delta_unit = (((data[0] & 0x08) >> 3) == 0);
537
538
539
540
541
542
543
544
545
546

    trim = 0;

    /* Strip trailing \0 for subtitles */
    if (pad->map.is_ogm_text) {
      while (bytes && data[bytes - 1] == 0) {
        trim++;
        bytes--;
      }
    }
547
548
  } else {
    offset = 0;
549
    trim = 0;
550
551
  }

552
  /* get timing info for the packet */
553
  duration = gst_ogg_stream_get_packet_duration (&pad->map, packet);
554
  GST_DEBUG_OBJECT (ogg, "packet duration %" G_GUINT64_FORMAT, duration);
555

David Schleef's avatar
David Schleef committed
556
  if (packet->b_o_s) {
557
558
559
560
    out_timestamp = GST_CLOCK_TIME_NONE;
    out_duration = GST_CLOCK_TIME_NONE;
    out_offset = 0;
    out_offset_end = -1;
David Schleef's avatar
David Schleef committed
561
562
  } else {
    if (packet->granulepos != -1) {
563
564
565
566
      pad->current_granule = gst_ogg_stream_granulepos_to_granule (&pad->map,
          packet->granulepos);
      pad->keyframe_granule =
          gst_ogg_stream_granulepos_to_key_granule (&pad->map,
David Schleef's avatar
David Schleef committed
567
          packet->granulepos);
568
569
      GST_DEBUG_OBJECT (ogg, "new granule %" G_GUINT64_FORMAT,
          pad->current_granule);
570
    } else if (ogg->segment.rate > 0.0 && pad->current_granule != -1) {
571
572
573
      pad->current_granule += duration;
      GST_DEBUG_OBJECT (ogg, "interpollating granule %" G_GUINT64_FORMAT,
          pad->current_granule);
David Schleef's avatar
David Schleef committed
574
    }
575
576
577
578
579
580
    if (ogg->segment.rate < 0.0 && packet->granulepos == -1) {
      /* negative rates, only set timestamp on the packets with a granulepos */
      out_timestamp = -1;
      out_duration = -1;
      out_offset = -1;
      out_offset_end = -1;
581
    } else {
582
583
584
585
586
587
588
589
590
591
592
593
      /* we only push buffers after we have a valid granule. This is done so that
       * we nicely skip packets without a timestamp after a seek. This is ok
       * because we base or seek on the packet after the page with the smaller
       * timestamp. */
      if (pad->current_granule == -1)
        goto no_timestamp;

      if (pad->map.is_ogm) {
        out_timestamp = gst_ogg_stream_granule_to_time (&pad->map,
            pad->current_granule);
        out_duration = gst_util_uint64_scale (duration,
            GST_SECOND * pad->map.granulerate_d, pad->map.granulerate_n);
594
595
596
597
      } else if (pad->is_sparse) {
        out_timestamp = gst_ogg_stream_granule_to_time (&pad->map,
            pad->current_granule);
        out_duration = GST_CLOCK_TIME_NONE;
598
599
600
601
602
603
604
605
606
607
608
609
      } else {
        out_timestamp = gst_ogg_stream_granule_to_time (&pad->map,
            pad->current_granule - duration);
        out_duration =
            gst_ogg_stream_granule_to_time (&pad->map,
            pad->current_granule) - out_timestamp;
      }
      out_offset_end =
          gst_ogg_stream_granule_to_granulepos (&pad->map, pad->current_granule,
          pad->keyframe_granule);
      out_offset =
          gst_ogg_stream_granule_to_time (&pad->map, pad->current_granule);
610
    }
David Schleef's avatar
David Schleef committed
611
  }
612

613
  /* check for invalid buffer sizes */
614
  if (G_UNLIKELY (offset + trim >= packet->bytes))
615
616
617
618
    goto empty_packet;

  ret =
      gst_pad_alloc_buffer_and_set_caps (GST_PAD_CAST (pad),
619
620
      GST_BUFFER_OFFSET_NONE, packet->bytes - offset - trim,
      GST_PAD_CAPS (pad), &buf);
621
622
623
624
625
626

  /* combine flows */
  cret = gst_ogg_demux_combine_flows (ogg, pad, ret);
  if (ret != GST_FLOW_OK)
    goto no_buffer;

627
628
629
630
  /* set delta flag for OGM content */
  if (delta_unit)
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);

631
  /* copy packet in buffer */
632
  memcpy (buf->data, packet->packet + offset, packet->bytes - offset - trim);
633
634
635
636
637
638

  GST_BUFFER_TIMESTAMP (buf) = out_timestamp;
  GST_BUFFER_DURATION (buf) = out_duration;
  GST_BUFFER_OFFSET (buf) = out_offset;
  GST_BUFFER_OFFSET_END (buf) = out_offset_end;

639
640
641
642
643
644
  /* Mark discont on the buffer */
  if (pad->discont) {
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
    pad->discont = FALSE;
  }

645
646
  pad->last_stop = ogg->segment.last_stop;

647
648
649
  /* don't push the header packets when we are asked to skip them */
  if (!packet->b_o_s || push_headers) {
    ret = gst_pad_push (GST_PAD_CAST (pad), buf);
650

651
652
653
    /* combine flows */
    cret = gst_ogg_demux_combine_flows (ogg, pad, ret);
  }
654

655
  /* we're done with skeleton stuff */
David Schleef's avatar
David Schleef committed
656
  if (pad->map.is_skeleton)
657
    goto done;
658

659
  /* check if valid granulepos, then we can calculate the current
Wim Taymans's avatar
Wim Taymans committed
660
661
662
   * position. We know the granule for each packet but we only want to update
   * the last_stop when we have a valid granulepos on the packet because else
   * our time jumps around for the different streams. */
663
664
  if (packet->granulepos < 0)
    goto done;
665

666
  /* convert to time */
David Schleef's avatar
David Schleef committed
667
668
  current_time = gst_ogg_stream_get_end_time_for_granulepos (&pad->map,
      packet->granulepos);
669
670

  /* convert to stream time */
671
672
673
674
675
676
677
678
  if ((chain = pad->chain)) {
    gint64 chain_start = 0;

    if (chain->segment_start != GST_CLOCK_TIME_NONE)
      chain_start = chain->segment_start;

    current_time = current_time - chain_start + chain->begin_time;
  }
679
680
681

  /* and store as the current position */
  gst_segment_set_last_stop (&ogg->segment, GST_FORMAT_TIME, current_time);
682

683
684
685
686
  GST_DEBUG_OBJECT (ogg, "ogg current time %" GST_TIME_FORMAT,
      GST_TIME_ARGS (current_time));

done:
687
688
  /* return combined flow result */
  return cret;
689
690

  /* special cases */
691
692
693
694
695
696
697
698
699
700
701
702
empty_packet:
  {
    GST_DEBUG_OBJECT (ogg, "Skipping empty packet");
    cret = gst_ogg_demux_combine_flows (ogg, pad, GST_FLOW_OK);
    goto done;
  }
no_timestamp:
  {
    GST_DEBUG_OBJECT (ogg, "skipping packet: no valid granule found yet");
    cret = gst_ogg_demux_combine_flows (ogg, pad, GST_FLOW_OK);
    goto done;
  }
703
704
705
no_buffer:
  {
    GST_DEBUG_OBJECT (ogg,
706
        "%p could not get buffer from peer %08x, %d (%s), combined %d (%s)",
David Schleef's avatar
David Schleef committed
707
        pad, pad->map.serialno, ret, gst_flow_get_name (ret),
708
709
        cret, gst_flow_get_name (cret));
    goto done;
710
  }
711
712
}

713
714
715
716
717
718
/* submit a packet to the oggpad, this function will run the
 * typefind code for the pad if this is the first packet for this
 * stream 
 */
static GstFlowReturn
gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet)
719
{
720
  gint64 granule;
721
  GstFlowReturn ret = GST_FLOW_OK;
722
723
724

  GstOggDemux *ogg = pad->ogg;

David Schleef's avatar
David Schleef committed
725
726
  GST_DEBUG_OBJECT (ogg, "%p submit packet serial %08x", pad,
      pad->map.serialno);
727

728
  if (!pad->have_type) {
729
730
731
732
    if (!ogg->have_fishead && packet->bytes == SKELETON_FISHEAD_SIZE &&
        !memcmp (packet->packet, "fishead\0", 8)) {
      gst_ogg_pad_parse_skeleton_fishead (pad, packet);
    }
David Schleef's avatar
David Schleef committed
733
    pad->have_type = gst_ogg_stream_setup_map (&pad->map, packet);
734
735
736
    if (!pad->have_type) {
      pad->map.caps = gst_caps_new_simple ("application/x-unknown", NULL);
    }
David Schleef's avatar
David Schleef committed
737
738
    if (pad->map.caps) {
      gst_pad_set_caps (GST_PAD (pad), pad->map.caps);
739
740
    } else {
      GST_WARNING_OBJECT (ogg, "stream parser didn't create src pad caps");
David Schleef's avatar
David Schleef committed
741
    }
742
743
  }

744
745
746
747
748
  if (ogg->have_fishead && packet->bytes >= SKELETON_FISBONE_MIN_SIZE &&
      !memcmp (packet->packet, "fisbone\0", 8)) {
    gst_ogg_pad_parse_skeleton_fisbone (pad, packet);
  }

749
750
  granule = gst_ogg_stream_granulepos_to_granule (&pad->map,
      packet->granulepos);
751
  if (granule != -1) {
752
    GST_DEBUG_OBJECT (ogg, "%p has granulepos %" G_GINT64_FORMAT, pad, granule);
753
754
755
    pad->current_granule = granule;
  }

756
757
758
759
760
  /* restart header packet count when seeing a b_o_s page;
   * particularly useful following a seek or even following chain finding */
  if (packet->b_o_s) {
    GST_DEBUG_OBJECT (ogg, "b_o_s packet, resetting header packet count");
    pad->map.n_header_packets_seen = 0;
761
762
    if (!pad->map.have_headers) {
      GST_DEBUG_OBJECT (ogg, "clearing header packets");
763
      g_list_foreach (pad->map.headers, (GFunc) _ogg_packet_free, NULL);
764
765
766
      g_list_free (pad->map.headers);
      pad->map.headers = NULL;
    }
767
768
  }

David Schleef's avatar
David Schleef committed
769
770
771
772
773
  /* Overload the value of b_o_s in ogg_packet with a flag whether or
   * not this is a header packet.  Maybe some day this could be cleaned
   * up.  */
  packet->b_o_s = gst_ogg_stream_packet_is_header (&pad->map, packet);
  if (!packet->b_o_s) {
774
    pad->map.have_headers = TRUE;
David Schleef's avatar
David Schleef committed
775
776
    if (pad->start_time == GST_CLOCK_TIME_NONE) {
      gint64 duration = gst_ogg_stream_get_packet_duration (&pad->map, packet);
777
      GST_DEBUG ("duration %" G_GINT64_FORMAT, duration);
David Schleef's avatar
David Schleef committed
778
779
      if (duration != -1) {
        pad->map.accumulated_granule += duration;
780
781
        GST_DEBUG ("accumulated granule %" G_GINT64_FORMAT,
            pad->map.accumulated_granule);
David Schleef's avatar
David Schleef committed
782
      }
783

David Schleef's avatar
David Schleef committed
784
785
      if (packet->granulepos != -1) {
        ogg_int64_t start_granule;
786
787
788
789
        gint64 granule;

        granule = gst_ogg_stream_granulepos_to_granule (&pad->map,
            packet->granulepos);
David Schleef's avatar
David Schleef committed
790

791
792
793
794
        if (granule > pad->map.accumulated_granule)
          start_granule = granule - pad->map.accumulated_granule;
        else
          start_granule = 0;
David Schleef's avatar
David Schleef committed
795
796
797

        pad->start_time = gst_ogg_stream_granule_to_time (&pad->map,
            start_granule);
David Schleef's avatar
David Schleef committed
798
799
800
        GST_DEBUG ("start time %" G_GINT64_FORMAT, pad->start_time);
      } else {
        packet->granulepos = gst_ogg_stream_granule_to_granulepos (&pad->map,
801
            pad->map.accumulated_granule, pad->keyframe_granule);
David Schleef's avatar
David Schleef committed
802
803
804
805
      }
    }
  } else {
    pad->map.n_header_packets_seen++;
806
    if (!pad->map.have_headers) {
807
808
      pad->map.headers =
          g_list_append (pad->map.headers, _ogg_packet_copy (packet));
809
810
      GST_DEBUG ("keeping header packet %d", pad->map.n_header_packets_seen);
    }
811
  }
David Schleef's avatar
David Schleef committed
812

813
814
815
816
  /* we know the start_time of the pad data, see if we
   * can activate the complete chain if this is a dynamic
   * chain. */
  if (pad->start_time != GST_CLOCK_TIME_NONE) {
817
818
    GstOggChain *chain = pad->chain;

819
    /* check if complete chain has start time */
820
821
    if (chain == ogg->building_chain) {

822
823
      /* see if we have enough info to activate the chain, we have enough info
       * when all streams have a valid start time. */
824
      if (gst_ogg_demux_collect_chain_info (ogg, chain)) {
825
826
        GstEvent *event;

827
        GST_DEBUG_OBJECT (ogg, "segment_start: %" GST_TIME_FORMAT,
828
            GST_TIME_ARGS (chain->segment_start));
829
        GST_DEBUG_OBJECT (ogg, "segment_stop:  %" GST_TIME_FORMAT,
830
            GST_TIME_ARGS (chain->segment_stop));
831
        GST_DEBUG_OBJECT (ogg, "segment_time:  %" GST_TIME_FORMAT,
832
            GST_TIME_ARGS (chain->begin_time));
833

834
835
        /* create the newsegment event we are going to send out */
        event = gst_event_new_new_segment (FALSE, ogg->segment.rate,
836
837
            GST_FORMAT_TIME, chain->segment_start, chain->segment_stop,
            chain->begin_time);
838
        gst_event_set_seqnum (event, ogg->seqnum);
839

840
        gst_ogg_demux_activate_chain (ogg, chain, event);
841
842

        ogg->building_chain = NULL;
843
      }
844
845
    }
  }
Wim Taymans's avatar
Wim Taymans committed
846

847
  /* if we are building a chain, store buffer for when we activate
848
849
   * it. This path is taken if we operate in streaming mode. */
  if (ogg->building_chain) {
850
851
    /* bos packets where stored in the header list so we can discard
     * them here*/
852
853
    if (!packet->b_o_s)
      ret = gst_ogg_demux_queue_data (pad, packet);
854
  }
855
856
  /* else we are completely streaming to the peer */
  else {
857
    ret = gst_ogg_demux_chain_peer (pad, packet, !ogg->pullmode);
Wim Taymans's avatar
Wim Taymans committed
858
  }
859
  return ret;
860
861
}

862
863
/* flush at most @npackets from the stream layer. All packets if 
 * @npackets is 0;
864
865
 */
static GstFlowReturn
866
gst_ogg_pad_stream_out (GstOggPad * pad, gint npackets)
867
868
{
  GstFlowReturn result = GST_FLOW_OK;
869
  gboolean done = FALSE;
870
871
  GstOggDemux *ogg;

872
  ogg = pad->ogg;
873

874
  while (!done) {
875
876
877
    int ret;
    ogg_packet packet;

David Schleef's avatar
David Schleef committed
878
    ret = ogg_stream_packetout (&pad->map.stream, &packet);
879
880
    switch (ret) {
      case 0:
881
        GST_LOG_OBJECT (ogg, "packetout done");
882
883
884
        done = TRUE;
        break;
      case -1:
885
        GST_LOG_OBJECT (ogg, "packetout discont");
886
        gst_ogg_chain_mark_discont (pad->chain);
887
888
        break;
      case 1:
889
        GST_LOG_OBJECT (ogg, "packetout gave packet of size %ld", packet.bytes);
890
        result = gst_ogg_pad_submit_packet (pad, &packet);
891
892
        if (GST_FLOW_IS_FATAL (result))
          goto could_not_submit;
893
894
895
896
897
898
899
900
        break;
      default:
        GST_WARNING_OBJECT (ogg,
            "invalid return value %d for ogg_stream_packetout, resetting stream",
            ret);
        gst_ogg_pad_reset (pad);
        break;
    }
901
902
903
904
905
906
907
908
909
910
911
    if (npackets > 0) {
      npackets--;
      done = (npackets == 0);
    }
  }
  return result;

  /* ERRORS */
could_not_submit:
  {
    GST_WARNING_OBJECT (ogg,
David Schleef's avatar
David Schleef committed
912
        "could not submit packet for stream %08x, error: %d", pad->map.serialno,
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
        result);
    gst_ogg_pad_reset (pad);
    return result;
  }
}

/* submit a page to an oggpad, this function will then submit all
 * the packets in the page.
 */
static GstFlowReturn
gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page)
{
  GstFlowReturn result = GST_FLOW_OK;
  GstOggDemux *ogg;
  gboolean continued = FALSE;

  ogg = pad->ogg;

Wim Taymans's avatar
Wim Taymans committed
931
932
  /* for negative rates we read pages backwards and must therefore be carefull
   * with continued pages */
933
934
935
936
937
938
939
940
  if (ogg->segment.rate < 0.0) {
    gint npackets;

    continued = ogg_page_continued (page);

    /* number of completed packets in the page */
    npackets = ogg_page_packets (page);
    if (!continued) {
Wim Taymans's avatar
Wim Taymans committed
941
942
943
944
945
      /* page is not continued so it contains at least one packet start. It's
       * possible that no packet ends on this page (npackets == 0). In that
       * case, the next (continued) page(s) we kept contain the remainder of the
       * packets. We mark npackets=1 to make us start decoding the pages in the
       * remainder of the algorithm. */
946
947
948
949
950
951
952
953
954
955
956
      if (npackets == 0)
        npackets = 1;
    }
    GST_LOG_OBJECT (ogg, "continued: %d, %d packets", continued, npackets);

    if (npackets == 0) {
      GST_LOG_OBJECT (ogg, "no decodable packets, we need a previous page");
      goto done;
    }
  }

David Schleef's avatar
David Schleef committed
957
  if (ogg_stream_pagein (&pad->map.stream, page) != 0)
958
959
    goto choked;

Wim Taymans's avatar
Wim Taymans committed
960
961
  /* flush all packets in the stream layer, this might not give a packet if
   * the page had no packets finishing on the page (npackets == 0). */
962
963
964
965
966
967
968
969
970
971
  result = gst_ogg_pad_stream_out (pad, 0);

  if (pad->continued) {
    ogg_packet packet;

    /* now send the continued pages to the stream layer */
    while (pad->continued) {
      ogg_page *p = (ogg_page *) pad->continued->data;

      GST_LOG_OBJECT (ogg, "submitting continued page %p", p);
David Schleef's avatar
David Schleef committed
972
      if (ogg_stream_pagein (&pad->map.stream, p) != 0)
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
        goto choked;

      pad->continued = g_list_delete_link (pad->continued, pad->continued);

      /* free the page */
      gst_ogg_page_free (p);
    }

    GST_LOG_OBJECT (ogg, "flushing last continued packet");
    /* flush 1 continued packet in the stream layer */
    result = gst_ogg_pad_stream_out (pad, 1);

    /* flush all remaining packets, we pushed them in the previous round.
     * We don't use _reset() because we still want to get the discont when
     * we submit a next page. */
David Schleef's avatar
David Schleef committed
988
    while (ogg_stream_packetout (&pad->map.stream, &packet) != 0);
989
  }
990
991
992
993
994
995
996
997
998
999

done:
  /* keep continued pages (only in reverse mode) */
  if (continued) {
    ogg_page *p = gst_ogg_page_copy (page);

    GST_LOG_OBJECT (ogg, "keeping continued page %p", p);
    pad->continued = g_list_prepend (pad->continued, p);
  }

1000
  return result;
1001
1002
1003
1004

choked:
  {
    GST_WARNING_OBJECT (ogg,
1005
        "ogg stream choked on page (serial %08x), resetting stream",
David Schleef's avatar
David Schleef committed
1006
        pad->map.serialno);
1007
1008
1009
1010
    gst_ogg_pad_reset (pad);
    /* we continue to recover */
    return GST_FLOW_OK;
  }
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
}


static GstOggChain *
gst_ogg_chain_new (GstOggDemux * ogg)
{
  GstOggChain *chain = g_new0 (GstOggChain, 1);

  GST_DEBUG_OBJECT (ogg, "creating new chain %p", chain);
  chain->ogg = ogg;
  chain->offset = -1;
  chain->bytes = -1;
  chain->have_bos = FALSE;
  chain->streams = g_array_new (FALSE, TRUE, sizeof (GstOggPad *));
1025
  chain->begin_time = GST_CLOCK_TIME_NONE;
1026
  chain->segment_start = GST_CLOCK_TIME_NONE;
1027
  chain->segment_stop = GST_CLOCK_TIME_NONE;
1028
  chain->total_time = GST_CLOCK_TIME_NONE;
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040

  return chain;
}

static void
gst_ogg_chain_free (GstOggChain * chain)
{
  gint