gstoggdemux.c 57.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 22 23 24 25 26
 *
 * 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>
#include <ogg/ogg.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
27
#include <string.h>
28

29
#define CHUNKSIZE (8500)        /* this is out of vorbisfile */
30

31 32 33 34 35 36 37
enum
{
  OV_EREAD = -1,
  OV_EFAULT = -2,
  OV_FALSE = -3,
  OV_EOF = -4,
};
38

39
GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_debug);
40
GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_setup_debug);
41 42
#define GST_CAT_DEFAULT gst_ogg_demux_debug

43 44 45 46 47 48 49 50 51
#define GST_TYPE_OGG_PAD (gst_ogg_pad_get_type())
#define GST_OGG_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_PAD, GstOggPad))
#define GST_OGG_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_PAD, GstOggPad))
#define GST_IS_OGG_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_PAD))
#define GST_IS_OGG_PAD_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_PAD))

typedef struct _GstOggPad GstOggPad;
typedef struct _GstOggPadClass GstOggPadClass;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
52
#define GST_TYPE_OGG_DEMUX (gst_ogg_demux_get_type())
53 54 55 56 57
#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))

58 59
static GType gst_ogg_demux_get_type (void);

60 61 62
typedef struct _GstOggDemux GstOggDemux;
typedef struct _GstOggDemuxClass GstOggDemuxClass;

63 64
/* all information needed for one ogg chain (relevant for chained bitstreams) */
typedef struct _GstOggChain
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
65
{
66
  GstOggDemux *ogg;
67

68 69 70
  gint64 offset;                /* starting offset of chain */
  gint64 end_offset;            /* end offset of chain */
  gint64 bytes;                 /* number of bytes */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
71

72
  gboolean have_bos;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
73

74
  GArray *streams;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
75

76 77 78 79
  GstClockTime total_time;      /* the total time of this chain, this is the MAX of
                                   the totals of all streams */
  GstClockTime start_time;      /* the timestamp of the first sample */
  GstClockTime last_time;       /* the timestamp of the last page == last sample */
80

81 82 83
  GstClockTime begin_time;      /* when this chain starts in the stream */

} GstOggChain;
84

85
/* different modes for the pad */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
86 87
typedef enum
{
88 89 90
  GST_OGG_PAD_MODE_INIT,        /* we are feeding our internal decoder to get info */
  GST_OGG_PAD_MODE_STREAMING,   /* we are streaming buffers to the outside */
} GstOggPadMode;
91

92 93
/* all information needed for one ogg stream */
struct _GstOggPad
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
94
{
95
  GstRealPad pad;               /* subclass GstRealPad */
96

97
  GstOggPadMode mode;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
98

99 100 101
  GstPad *elem_pad;             /* sinkpad of internal element */
  GstElement *element;          /* internal element */
  GstPad *elem_out;             /* our sinkpad to receive buffers form the internal element */
102

103 104
  GstOggChain *chain;           /* the chain we are part of */
  GstOggDemux *ogg;             /* the ogg demuxer we are part of */
105

106
  GList *headers;
107

108 109 110
  gint serialno;
  gint64 packetno;
  gint64 offset;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
111

112 113 114
  GstEvent *new_segment;
  gint64 start;
  gint64 stop;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
115

116
  gint64 current_granule;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
117

118
  GstClockTime start_time;      /* the timestamp of the first sample */
119

120 121
  gint64 first_granule;         /* the granulepos of first page == first sample in next page */
  GstClockTime first_time;      /* the timestamp of the second page */
122

123 124 125 126 127 128
  GstClockTime last_time;       /* the timestamp of the last page == last sample */
  gint64 last_granule;          /* the granulepos of the last page */

  GstClockTime total_time;      /* the total time of this stream */

  ogg_stream_state stream;
129 130
};

131
struct _GstOggPadClass
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
132
{
133
  GstRealPadClass parent_class;
134 135
};

136 137
typedef enum
{
138 139 140
  OGG_STATE_NEW_CHAIN,
  OGG_STATE_STREAMING,
} OggState;
141

142 143 144 145
#define GST_CHAIN_LOCK(ogg)	g_mutex_lock((ogg)->chain_lock)
#define GST_CHAIN_UNLOCK(ogg)	g_mutex_unlock((ogg)->chain_lock)

struct _GstOggDemux
146
{
147
  GstElement element;
148

149
  GstPad *sinkpad;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
150

151 152
  gint64 length;
  gint64 offset;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
153

154 155
  OggState state;
  gboolean seekable;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
156

157
  gboolean need_chains;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
158

159 160 161
  /* state */
  GMutex *chain_lock;           /* we need the lock to protect the chains */
  GArray *chains;               /* list of chains we know */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
162

163 164 165 166
  GstClockTime total_time;      /* the total time of this ogg, this is the sum of
                                   the totals of all chains */
  GstOggChain *current_chain;
  GstOggChain *building_chain;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
167

168 169 170
  /* ogg stuff */
  ogg_sync_state sync;
};
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
171

172 173 174 175
struct _GstOggDemuxClass
{
  GstElementClass parent_class;
};
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
176

177 178 179 180 181 182
static GstStaticPadTemplate internaltemplate =
GST_STATIC_PAD_TEMPLATE ("internal",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS_ANY);

183
static gboolean gst_ogg_demux_perform_seek (GstOggDemux * ogg, gint64 pos);
184

185 186 187 188
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
189 190

#if 0
191 192
static const GstFormat *gst_ogg_pad_formats (GstPad * pad);
static const GstEventMask *gst_ogg_pad_event_masks (GstPad * pad);
Wim Taymans's avatar
Wim Taymans committed
193
#endif
194
static const GstQueryType *gst_ogg_pad_query_types (GstPad * pad);
Wim Taymans's avatar
Wim Taymans committed
195
static gboolean gst_ogg_pad_src_query (GstPad * pad, GstQuery * query);
196 197 198
static gboolean gst_ogg_pad_event (GstPad * pad, GstEvent * event);
static GstCaps *gst_ogg_pad_getcaps (GstPad * pad);
static GstCaps *gst_ogg_type_find (ogg_packet * packet);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
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
static GstRealPadClass *ogg_pad_parent_class = NULL;

static GType
gst_ogg_pad_get_type (void)
{
  static GType ogg_pad_type = 0;

  if (!ogg_pad_type) {
    static const GTypeInfo ogg_pad_info = {
      sizeof (GstOggPadClass),
      NULL,
      NULL,
      (GClassInitFunc) gst_ogg_pad_class_init,
      NULL,
      NULL,
      sizeof (GstOggPad),
      0,
      (GInstanceInitFunc) gst_ogg_pad_init,
    };

    ogg_pad_type =
        g_type_register_static (GST_TYPE_REAL_PAD, "GstOggPad", &ogg_pad_info,
        0);
  }
  return ogg_pad_type;
}
226

227 228
static void
gst_ogg_pad_class_init (GstOggPadClass * klass)
229
{
230
  GObjectClass *gobject_class;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
231

232
  gobject_class = (GObjectClass *) klass;
233

234
  ogg_pad_parent_class = g_type_class_ref (G_TYPE_OBJECT);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
235

236 237 238
  gobject_class->dispose = gst_ogg_pad_dispose;
  gobject_class->finalize = gst_ogg_pad_finalize;
}
239

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
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->first_granule = -1;
  pad->last_granule = -1;
  pad->current_granule = -1;

  pad->start_time = -1;
  pad->first_time = -1;
  pad->last_time = -1;

  pad->headers = NULL;
263
}
264

265
static void
266
gst_ogg_pad_dispose (GObject * object)
267
{
268
  GstOggPad *pad = GST_OGG_PAD (object);
269

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
  gst_object_replace ((GstObject **) (&pad->elem_pad), NULL);
  gst_object_replace ((GstObject **) (&pad->element), NULL);
  gst_object_replace ((GstObject **) (&pad->elem_out), NULL);

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

  g_list_foreach (pad->headers, (GFunc) gst_data_unref, NULL);
  g_list_free (pad->headers);
  pad->headers = NULL;

  if (pad->new_segment) {
    gst_event_unref (pad->new_segment);
    pad->new_segment = NULL;
  }
  ogg_stream_reset (&pad->stream);

  G_OBJECT_CLASS (ogg_pad_parent_class)->dispose (object);
288
}
289

290
static void
291
gst_ogg_pad_finalize (GObject * object)
292
{
293
  GstOggPad *pad = GST_OGG_PAD (object);
294

295
  ogg_stream_clear (&pad->stream);
Johan Dahlin's avatar
Johan Dahlin committed
296

297
  G_OBJECT_CLASS (ogg_pad_parent_class)->finalize (object);
298 299
}

Wim Taymans's avatar
Wim Taymans committed
300
#if 0
301
static const GstFormat *
302
gst_ogg_pad_formats (GstPad * pad)
303 304
{
  static GstFormat src_formats[] = {
305 306
    GST_FORMAT_DEFAULT,         /* time */
    GST_FORMAT_TIME,            /* granulepos */
307 308 309 310 311 312 313 314 315 316
    0
  };
  static GstFormat sink_formats[] = {
    GST_FORMAT_BYTES,
    GST_FORMAT_DEFAULT,         /* bytes */
    0
  };

  return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats);
}
Wim Taymans's avatar
Wim Taymans committed
317
#endif
318

Wim Taymans's avatar
Wim Taymans committed
319
#if 0
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
320
static const GstEventMask *
321
gst_ogg_pad_event_masks (GstPad * pad)
322
{
323
  static const GstEventMask src_event_masks[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
324 325
    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH},
    {0,}
326
  };
327

328
  return src_event_masks;
329
}
Wim Taymans's avatar
Wim Taymans committed
330
#endif
331

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
332
static const GstQueryType *
333
gst_ogg_pad_query_types (GstPad * pad)
334
{
335
  static const GstQueryType query_types[] = {
336 337 338
    GST_QUERY_POSITION,
    0
  };
339

340
  return query_types;
341 342
}

343 344
static GstCaps *
gst_ogg_pad_getcaps (GstPad * pad)
345
{
346
  return gst_caps_ref (GST_PAD_CAPS (pad));
347 348
}

349
static gboolean
Wim Taymans's avatar
Wim Taymans committed
350
gst_ogg_pad_src_query (GstPad * pad, GstQuery * query)
351
{
352
  gboolean res = TRUE;
353 354 355
  GstOggDemux *ogg;
  GstOggPad *cur;

356 357
  ogg = GST_OGG_DEMUX (GST_PAD_PARENT (pad));
  cur = GST_OGG_PAD (pad);
358

Wim Taymans's avatar
Wim Taymans committed
359
  switch (GST_QUERY_TYPE (query)) {
360
    case GST_QUERY_POSITION:
Wim Taymans's avatar
Wim Taymans committed
361
      gst_query_set_position (query, GST_FORMAT_TIME, -1, ogg->total_time);
362
      break;
Wim Taymans's avatar
Wim Taymans committed
363 364 365
    case GST_QUERY_CONVERT:
      /* hmm .. */
      res = FALSE;
366 367
      break;
    default:
368
      res = FALSE;
369 370
      break;
  }
371

372 373 374 375
  return res;
}

static gboolean
376
gst_ogg_pad_event (GstPad * pad, GstEvent * event)
377
{
378
  gboolean res;
379
  GstOggDemux *ogg;
380
  GstOggPad *cur;
381

382 383
  ogg = GST_OGG_DEMUX (GST_PAD_PARENT (pad));
  cur = GST_OGG_PAD (pad);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
384

385 386
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
387
    {
388
      gint64 offset;
389

390 391 392 393
      /* can't seek if we are not seekable */
      if (!ogg->seekable) {
        res = FALSE;
        goto done_unref;
394
      }
395 396 397 398
      /* we can only seek on time */
      if (GST_EVENT_SEEK_FORMAT (event) != GST_FORMAT_TIME) {
        res = FALSE;
        goto done_unref;
399
      }
400
      offset = GST_EVENT_SEEK_OFFSET (event);
401
      gst_event_unref (event);
402 403 404 405

      /* now do the seek */
      res = gst_ogg_demux_perform_seek (ogg, offset);
      break;
406
    }
407
    default:
408 409
      res = gst_pad_event_default (pad, event);
      break;
410 411
  }

412
  return res;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
413

414
done_unref:
415
  gst_event_unref (event);
416 417 418
  return res;
}

419
static void
420
gst_ogg_pad_reset (GstOggPad * pad)
421
{
422 423
  ogg_stream_reset (&pad->stream);
  /* FIXME: need a discont here */
424 425
}

426 427
/* the filter function for selecting the elements we can use in
 *  * autoplugging */
428
static gboolean
429
gst_ogg_demux_factory_filter (GstPluginFeature * feature, GstCaps * caps)
430
{
431 432
  guint rank;
  const gchar *klass;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
433

434 435 436 437 438 439 440 441 442
  /* we only care about element factories */
  if (!GST_IS_ELEMENT_FACTORY (feature))
    return FALSE;

  klass = gst_element_factory_get_klass (GST_ELEMENT_FACTORY (feature));
  /* only demuxers and decoders can play */
  if (strstr (klass, "Demux") == NULL &&
      strstr (klass, "Decoder") == NULL && strstr (klass, "Parse") == NULL) {
    return FALSE;
443
  }
444

445 446 447 448
  /* only select elements with autoplugging rank */
  rank = gst_plugin_feature_get_rank (feature);
  if (rank < GST_RANK_MARGINAL)
    return FALSE;
449

450 451 452 453 454 455 456 457
  GST_DEBUG ("checking factory %s", GST_PLUGIN_FEATURE_NAME (feature));
  /* now see if it is compatible with the caps */
  {
    GstElementFactory *factory = GST_ELEMENT_FACTORY (feature);
    const GList *templates;
    GList *walk;

    /* get the templates from the element factory */
458
    templates = gst_element_factory_get_static_pad_templates (factory);
459 460

    for (walk = (GList *) templates; walk; walk = g_list_next (walk)) {
461
      GstStaticPadTemplate *templ = walk->data;
462

463 464 465
      /* we only care about the sink templates */
      if (templ->direction == GST_PAD_SINK) {
        GstCaps *intersect;
466

467
        /* try to intersect the caps with the caps of the template */
468 469
        intersect = gst_caps_intersect (caps,
            gst_static_caps_get (&templ->static_caps));
470 471 472 473 474 475 476 477

        /* check if the intersection is empty */
        if (!gst_caps_is_empty (intersect)) {
          /* non empty intersection, we can use this element */
          gst_caps_unref (intersect);
          goto found;
        }
        gst_caps_unref (intersect);
478
      }
479
    }
480
  }
481
  return FALSE;
482

483 484
found:
  return TRUE;
485
}
486

487 488 489
/* function used to sort element features */
static gint
compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2)
490
{
491
  gint diff;
492

493 494 495 496 497
  diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1);
  if (diff != 0)
    return diff;
  return strcmp (gst_plugin_feature_get_name (f2),
      gst_plugin_feature_get_name (f1));
498 499
}

500 501
static GstFlowReturn
gst_ogg_pad_internal_chain (GstPad * pad, GstBuffer * buffer)
502
{
503 504
  GstOggPad *oggpad;
  GstClockTime timestamp;
505

506
  oggpad = gst_pad_get_element_private (pad);
507

508 509 510
  timestamp = GST_BUFFER_TIMESTAMP (buffer);
  GST_DEBUG_OBJECT (oggpad, "received buffer from iternal pad, TS=%lld",
      timestamp);
511

512 513
  if (oggpad->start_time == -1)
    oggpad->start_time = timestamp;
514

515
  return GST_FLOW_OK;
516 517
}

518 519 520 521 522 523 524 525 526
/* runs typefind on the packet, which is assumed to be the first
 * packet in the stream.
 * 
 * Based on the type returned from the typefind function, an element
 * is created to help in conversion between granulepos and timestamps
 * so that we can do decent seeking.
 */
static gboolean
gst_ogg_pad_typefind (GstOggPad * pad, ogg_packet * packet)
527
{
528 529 530
  GstCaps *caps;
  GstElement *element = NULL;
  GstOggDemux *ogg = pad->ogg;
531

532 533
  if (GST_PAD_CAPS (pad) != NULL)
    return TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
534

535
  caps = gst_ogg_type_find (packet);
536

537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
  if (caps == NULL) {
    GST_WARNING_OBJECT (ogg,
        "couldn't find caps for stream with serial %08lx", pad->serialno);
    caps = gst_caps_new_simple ("application/octet-stream", NULL);
  } else {
    /* ogg requires you to use a decoder element to define the
     * meaning of granulepos etc so we make one. We only do this if
     * we are in the seeking mode. */
    if (ogg->seekable) {
      GList *factories;

      /* first filter out the interesting element factories */
      factories = gst_registry_pool_feature_filter (
          (GstPluginFeatureFilter) gst_ogg_demux_factory_filter, FALSE, caps);

      /* sort them according to their ranks */
      factories = g_list_sort (factories, (GCompareFunc) compare_ranks);

      /* then pick the first factory to create an element */
      if (factories) {
        element =
            gst_element_factory_create (GST_ELEMENT_FACTORY (factories->data),
            NULL);
        if (element) {
          /* this is ours */
          gst_object_ref (GST_OBJECT (element));
          gst_object_sink (GST_OBJECT (element));

          /* FIXME, it might not be named "sink" */
          pad->elem_pad = gst_element_get_pad (element, "sink");
          gst_element_set_state (element, GST_STATE_PAUSED);
568 569 570
          pad->elem_out =
              gst_pad_new_from_template (gst_static_pad_template_get
              (&internaltemplate), "internal");
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
          gst_pad_set_chain_function (pad->elem_out,
              gst_ogg_pad_internal_chain);
          gst_pad_set_element_private (pad->elem_out, pad);
          gst_pad_set_caps (pad->elem_out, gst_caps_new_any ());
          gst_pad_set_active (pad->elem_out, TRUE);

          /* and this pad may not be named src.. */
          gst_pad_link (gst_element_get_pad (element, "src"), pad->elem_out);
        }
      }
      g_list_free (factories);
    } else {
      pad->mode = GST_OGG_PAD_MODE_STREAMING;
    }
  }
  pad->element = element;
587

588 589 590 591
  gst_pad_set_caps (GST_PAD (pad), caps);
  gst_caps_unref (caps);

  return TRUE;
592 593
}

594 595 596 597 598 599
/* 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)
600
{
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
  GstBuffer *buf;
  gint64 granule;
  GstFlowReturn ret = GST_FLOW_OK;

  GstOggDemux *ogg = pad->ogg;

  GST_DEBUG_OBJECT (ogg,
      "%p submit packet serial %08lx, packetno %lld", pad, pad->serialno,
      pad->packetno);

  granule = packet->granulepos;
  if (granule != -1) {
    pad->current_granule = granule;
    if (pad->first_granule == -1 && granule != 0) {
      pad->first_granule = granule;
    }
  }
  /* first packet, FIXME, do this in chain activation */
  if (pad->packetno == 0) {
    gst_ogg_pad_typefind (pad, packet);
  }
#if 0
  if (ogg->state != OGG_STATE_STREAMING) {
    GST_DEBUG_OBJECT (ogg, "%p collecting headers, state %d", pad, ogg->state);

    buf = gst_buffer_new_and_alloc (packet->bytes);
    memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes);
    gst_buffer_set_caps (buf, GST_PAD_CAPS (pad));
    GST_BUFFER_OFFSET (buf) = -1;
    GST_BUFFER_OFFSET_END (buf) = packet->granulepos;

    /* we are collecting the chain info, just need to queue the buffers */
    pad->headers = g_list_append (pad->headers, buf);

    goto done;
  }
#endif

  /* stream packet to peer plugin */
  if (pad->mode == GST_OGG_PAD_MODE_STREAMING) {
    buf =
        gst_pad_alloc_buffer (GST_PAD (pad), GST_BUFFER_OFFSET_NONE,
        packet->bytes, GST_PAD_CAPS (pad));

    GST_DEBUG_OBJECT (ogg,
        "%p streaming to peer serial %08lx, packetno %lld", pad, pad->serialno,
        pad->packetno);

    if (pad->new_segment) {
      ret = gst_pad_push_event (GST_PAD (pad), pad->new_segment);
      pad->new_segment = NULL;
    }
    if (buf) {
      memcpy (buf->data, packet->packet, packet->bytes);

      pad->offset = packet->granulepos;
      GST_BUFFER_OFFSET (buf) = -1;
      GST_BUFFER_OFFSET_END (buf) = packet->granulepos;

      ret = gst_pad_push (GST_PAD (pad), buf);
    } else {
      GST_DEBUG_OBJECT (ogg,
          "%p could not get buffer from peer %08lx, packetno %lld", pad,
          pad->serialno, pad->packetno);
    }
666
  } else {
667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
    /* initialize our internal decoder with packets */
    if (!pad->elem_pad) {
      GST_WARNING_OBJECT (ogg,
          "pad %08lx does not have elem_pad, no decoder ?", pad);
      return GST_FLOW_OK;
    }

    GST_DEBUG_OBJECT (ogg,
        "%p init decoder serial %08lx, packetno %lld", pad, pad->serialno,
        pad->packetno);

    buf = gst_buffer_new_and_alloc (packet->bytes);
    memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes);
    gst_buffer_set_caps (buf, GST_PAD_CAPS (pad));
    GST_BUFFER_OFFSET (buf) = -1;
    GST_BUFFER_OFFSET_END (buf) = packet->granulepos;

    ret = GST_RPAD_CHAINFUNC (pad->elem_pad) (pad->elem_pad, buf);
  }

Wim Taymans's avatar
Wim Taymans committed
687 688 689
#if 0
done:
#endif
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 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 777 778 779 780 781 782 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 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
  pad->packetno++;

  return ret;
}

/* 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)
{
  ogg_packet packet;
  int ret;
  gboolean done = FALSE;
  GstFlowReturn result = GST_FLOW_OK;
  GstOggDemux *ogg;

  ogg = GST_OGG_DEMUX (GST_PAD_PARENT (pad));

  if (ogg_stream_pagein (&pad->stream, page) != 0) {
    GST_WARNING_OBJECT (ogg,
        "ogg stream choked on page (serial %08lx), resetting stream",
        pad->serialno);
    gst_ogg_pad_reset (pad);
    return GST_FLOW_OK;
  }

  while (!done) {
    ret = ogg_stream_packetout (&pad->stream, &packet);
    GST_LOG_OBJECT (ogg, "packetout gave %d", ret);
    switch (ret) {
      case 0:
        done = TRUE;
        break;
      case -1:
        /* out of sync, could call gst_ogg_pad_reset() here but ogg can decode
         * the packet just fine. We should probably send a DISCONT though. */
        break;
      case 1:
        result = gst_ogg_pad_submit_packet (pad, &packet);
        if (result != GST_FLOW_OK) {
          GST_WARNING_OBJECT (ogg, "could not submit packet, error: %d",
              result);
          gst_ogg_pad_reset (pad);
          done = TRUE;
        }
        break;
      default:
        GST_WARNING_OBJECT (ogg,
            "invalid return value %d for ogg_stream_packetout, resetting stream",
            ret);
        gst_ogg_pad_reset (pad);
        break;
    }
  }
  return result;
}


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 *));

  return chain;
}

static void
gst_ogg_chain_free (GstOggChain * chain)
{
  gint i;

  for (i = 0; i < chain->streams->len; i++) {
    GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);

    gst_object_unref (GST_OBJECT (pad));
  }
  g_array_free (chain->streams, TRUE);
  chain->streams = NULL;
}

static GstOggPad *
gst_ogg_chain_new_stream (GstOggChain * chain, glong serialno)
{
  GstOggPad *ret;
  GstTagList *list;
  gchar *name;

  GST_DEBUG_OBJECT (chain->ogg, "creating new stream %08lx in chain %p",
      serialno, chain);

  ret = g_object_new (GST_TYPE_OGG_PAD, NULL);
  /* we own this one */
  gst_object_ref (GST_OBJECT (ret));
  gst_object_sink (GST_OBJECT (ret));

  list = gst_tag_list_new ();
  name = g_strdup_printf ("serial_%08lx", serialno);

  GST_RPAD_DIRECTION (ret) = GST_PAD_SRC;
  ret->chain = chain;
  ret->ogg = chain->ogg;
  gst_object_set_name (GST_OBJECT (ret), name);
  g_free (name);

  ret->serialno = serialno;
  if (ogg_stream_init (&ret->stream, serialno) != 0) {
    GST_ERROR ("Could not initialize ogg_stream struct for serial %08lx.",
        serialno);
    g_object_unref (G_OBJECT (ret));
    return NULL;
  }
  gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_SERIAL, serialno,
      NULL);
  //gst_element_found_tags (GST_ELEMENT (ogg), list);
  gst_tag_list_free (list);

  GST_LOG ("created new ogg src %p for stream with serial %08lx", ret,
      serialno);

  g_array_append_val (chain->streams, ret);

  return ret;
}

static GstOggPad *
gst_ogg_chain_get_stream (GstOggChain * chain, glong serialno)
{
  gint i;

  for (i = 0; i < chain->streams->len; i++) {
    GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);

    if (pad->serialno == serialno)
      return pad;
832
  }
833
  return NULL;
834 835
}

836 837 838 839 840 841 842 843 844 845 846 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 886 887 888
static gboolean
gst_ogg_chain_has_stream (GstOggChain * chain, glong serialno)
{
  return gst_ogg_chain_get_stream (chain, serialno) != NULL;
}

#define CURRENT_CHAIN(ogg) (&g_array_index ((ogg)->chains, GstOggChain, (ogg)->current_chain))

/* signals and args */
enum
{
  /* FILL ME */
  LAST_SIGNAL
};

enum
{
  ARG_0
      /* FILL ME */
};

static GstStaticPadTemplate ogg_demux_src_template_factory =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate ogg_demux_sink_template_factory =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/ogg")
    );

static void gst_ogg_demux_finalize (GObject * object);

//static const GstEventMask *gst_ogg_demux_get_event_masks (GstPad * pad);
//static const GstQueryType *gst_ogg_demux_get_query_types (GstPad * pad);
static GstOggChain *gst_ogg_demux_read_chain (GstOggDemux * ogg);
static gint gst_ogg_demux_read_end_chain (GstOggDemux * ogg,
    GstOggChain * chain);

static gboolean gst_ogg_demux_handle_event (GstPad * pad, GstEvent * event);
static void gst_ogg_demux_loop (GstOggPad * pad);
static GstFlowReturn gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer);
static gboolean gst_ogg_demux_sink_activate (GstPad * sinkpad,
    GstActivateMode mode);
static GstElementStateReturn gst_ogg_demux_change_state (GstElement * element);

static void gst_ogg_print (GstOggDemux * demux);

GST_BOILERPLATE (GstOggDemux, gst_ogg_demux, GstElement, GST_TYPE_ELEMENT);

889
static void
890
gst_ogg_demux_base_init (gpointer g_class)
891
{
892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
  static GstElementDetails gst_ogg_demux_details =
      GST_ELEMENT_DETAILS ("ogg demuxer",
      "Codec/Demuxer",
      "demux ogg streams (info about ogg: http://xiph.org)",
      "Wim Taymand <wim@fluendo.com>");

  gst_element_class_set_details (element_class, &gst_ogg_demux_details);

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&ogg_demux_sink_template_factory));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&ogg_demux_src_template_factory));
}
static void
gst_ogg_demux_class_init (GstOggDemuxClass * klass)
{
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gstelement_class->change_state = gst_ogg_demux_change_state;

  gobject_class->finalize = gst_ogg_demux_finalize;
915 916
}

917
static void
918
gst_ogg_demux_init (GstOggDemux * ogg)
919
{
920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
  /* create the sink pad */
  ogg->sinkpad =
      gst_pad_new_from_template (gst_static_pad_template_get
      (&ogg_demux_sink_template_factory), "sink");
  gst_pad_set_loop_function (ogg->sinkpad,
      (GstPadLoopFunction) gst_ogg_demux_loop);
  gst_pad_set_event_function (ogg->sinkpad, gst_ogg_demux_handle_event);
  gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_demux_chain);
  gst_pad_set_activate_function (ogg->sinkpad, gst_ogg_demux_sink_activate);
  gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad);

  ogg->chain_lock = g_mutex_new ();
  ogg->chains = g_array_new (FALSE, TRUE, sizeof (GstOggChain *));
  ogg->state = OGG_STATE_NEW_CHAIN;
}

static void
gst_ogg_demux_finalize (GObject * object)
{
  GstOggDemux *ogg;

  ogg = GST_OGG_DEMUX (object);

  g_mutex_free (ogg->chain_lock);
  ogg_sync_clear (&ogg->sync);

  if (G_OBJECT_CLASS (parent_class)->finalize)
    G_OBJECT_CLASS (parent_class)->finalize (object);
948 949 950
}

static gboolean
951 952 953
gst_ogg_demux_handle_event (GstPad * pad, GstEvent * event)
{
  GstOggDemux *ogg = GST_OGG_DEMUX (GST_PAD_PARENT (pad));
954

955 956 957 958 959 960 961 962 963
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_DISCONTINUOUS:
      GST_DEBUG_OBJECT (ogg, "got a discont event");
      ogg_sync_reset (&ogg->sync);
      gst_event_unref (event);
      break;
    default:
      return gst_pad_event_default (pad, event);
  }
964 965 966
  return TRUE;
}

967 968 969
/* submit the given buffer to the ogg sync.
 *
 * Returns the number of bytes submited.
970
 */
971 972 973 974 975 976 977 978 979 980 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
static gint
gst_ogg_demux_submit_buffer (GstOggDemux * ogg, GstBuffer * buffer)
{
  guint size;
  guint8 *data;
  gchar *oggbuffer;

  size = GST_BUFFER_SIZE (buffer);
  data = GST_BUFFER_DATA (buffer);

  oggbuffer = ogg_sync_buffer (&ogg->sync, size);
  memcpy (oggbuffer, data, size);
  ogg_sync_wrote (&ogg->sync, size);

  return size;
}

/* in random access mode this code updates the current read position
 * and resets the ogg sync buffer so that the next read will happen
 * from this new location.
 */
static void
gst_ogg_demux_seek (GstOggDemux * ogg, gint64 offset)
{
  GST_LOG_OBJECT (ogg, "seeking to %lld", offset);

  ogg->offset = offset;
  ogg_sync_reset (&ogg->sync);
}

/* read more data from the current offset and submit to
 * the ogg sync layer.
 *
 * Return number of bytes written.
 */
static gint
gst_ogg_demux_get_data (GstOggDemux * ogg)
{
  GstFlowReturn ret;
  GstBuffer *buffer;
  gint size;

1013
  GST_LOG_OBJECT (ogg, "get data %lld %lld", ogg->offset, ogg->length);
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 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 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
  if (ogg->offset == ogg->length)
    return 0;

  ret = gst_pad_pull_range (ogg->sinkpad, ogg->offset, CHUNKSIZE, &buffer);
  if (ret != GST_FLOW_OK)
    return -1;

  size = gst_ogg_demux_submit_buffer (ogg, buffer);
  gst_buffer_unref (buffer);

  return size;
}

/* Read the next page from the current offset.
 */
static gint64
gst_ogg_demux_get_next_page (GstOggDemux * ogg, ogg_page * og, gint64 boundary)
{
  gint64 end_offset = 0;

  GST_LOG_OBJECT (ogg, "get next page %lld", boundary);

  if (boundary > 0)
    end_offset = ogg->offset + boundary;

  while (TRUE) {
    glong more;

    if (boundary > 0 && ogg->offset >= end_offset) {
      GST_LOG_OBJECT (ogg, "offset %lld >= end_offset %lld", ogg->offset,
          end_offset);
      return OV_FALSE;
    }

    more = ogg_sync_pageseek (&ogg->sync, og);

    if (more < 0) {
      GST_LOG_OBJECT (ogg, "skipped %ld bytes", more);
      /* skipped n bytes */
      ogg->offset -= more;
    } else if (more == 0) {
      gint ret;

      /* send more paramedics */
      if (boundary == 0)
        return OV_FALSE;

      ret = gst_ogg_demux_get_data (ogg);
      if (ret == 0)
        return OV_EOF;
      if (ret < 0)
        return OV_EREAD;
    } else {
      /* got a page.  Return the offset at the page beginning,
         advance the internal offset past the page end */
      gint64 ret = ogg->offset;

      ogg->offset += more;
      /* need to reset as we do not keep track of the bytes we
       * sent to the sync layer */
      ogg_sync_reset (&ogg->sync);

      GST_LOG_OBJECT (ogg,
          "got page at %lld, serial %08lx, end at %lld, granule %lld", ret,
          ogg_page_serialno (og), ogg->offset, ogg_page_granulepos (og));

      return ret;
    }
  }
}

/* from the current offset, find the previous page, seeking backwards
 * until we find the page. */
static gint
gst_ogg_demux_get_prev_page (GstOggDemux * ogg, ogg_page * og)
{
  gint64 begin = ogg->offset;
  gint64 end = begin;
  gint64 ret;
  gint64 offset = -1;

  while (offset == -1) {
    begin -= CHUNKSIZE;
    if (begin < 0)
      begin = 0;

    gst_ogg_demux_seek (ogg, begin);

    /* now continue reading until we run out of data, if we find a page
     * start, we save it. It might not be the final page as there could be
     * another page after this one. */
    while (ogg->offset < end) {
      ret = gst_ogg_demux_get_next_page (ogg, og, end - ogg->offset);
      if (ret == OV_EREAD)
        return OV_EREAD;
      if (ret < 0) {
        break;
      } else {
        offset = ret;
      }
    }
  }

  /* we have the offset.  Actually snork and hold the page now */
  gst_ogg_demux_seek (ogg, offset);
  ret = gst_ogg_demux_get_next_page (ogg, og, CHUNKSIZE);
  if (ret < 0)
    /* this shouldn't be possible */
    return OV_EFAULT;

  return offset;
}

1127
static gboolean
1128
gst_ogg_demux_deactivate_current_chain (GstOggDemux * ogg)
1129
{
1130 1131
  gint i;
  GstOggChain *chain = ogg->current_chain;
1132

1133 1134 1135 1136 1137 1138 1139
  if (chain == NULL)
    return TRUE;

  for (i = 0; i < chain->streams->len; i++) {
    GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);

    gst_element_remove_pad (GST_ELEMENT (ogg), GST_PAD (pad));
1140
  }
1141 1142 1143 1144 1145
  /* if we cannot seek, we can destroy the chain completely */
  if (!ogg->seekable) {
    gst_ogg_chain_free (chain);
  }
  ogg->current_chain = NULL;
1146

1147 1148
  return TRUE;
}
1149

1150