gstoggdemux.c 58.2 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 189 190 191 192 193 194 195 196 197 198 199
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);
static const GstFormat *gst_ogg_pad_formats (GstPad * pad);
static const GstEventMask *gst_ogg_pad_event_masks (GstPad * pad);
static const GstQueryType *gst_ogg_pad_query_types (GstPad * pad);
static gboolean gst_ogg_pad_src_convert (GstPad * pad,
    GstFormat src_format, gint64 src_value,
    GstFormat * dest_format, gint64 * dest_value);
static gboolean gst_ogg_pad_src_query (GstPad * pad, GstQueryType type,
    GstFormat * format, gint64 * value);
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
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
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;
}
227

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

233
  gobject_class = (GObjectClass *) klass;
234

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

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

241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
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_event_mask_function (GST_PAD (pad),
      GST_DEBUG_FUNCPTR (gst_ogg_pad_event_masks));
  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_formats_function (GST_PAD (pad),
      GST_DEBUG_FUNCPTR (gst_ogg_pad_formats));
  gst_pad_set_convert_function (GST_PAD (pad),
      GST_DEBUG_FUNCPTR (gst_ogg_pad_src_convert));
  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;
270
}
271

272
static void
273
gst_ogg_pad_dispose (GObject * object)
274
{
275
  GstOggPad *pad = GST_OGG_PAD (object);
276

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
  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);
295
}
296

297
static void
298
gst_ogg_pad_finalize (GObject * object)
299
{
300
  GstOggPad *pad = GST_OGG_PAD (object);
301

302
  ogg_stream_clear (&pad->stream);
Johan Dahlin's avatar
Johan Dahlin committed
303

304
  G_OBJECT_CLASS (ogg_pad_parent_class)->finalize (object);
305 306
}

307
static const GstFormat *
308
gst_ogg_pad_formats (GstPad * pad)
309 310
{
  static GstFormat src_formats[] = {
311 312
    GST_FORMAT_DEFAULT,         /* time */
    GST_FORMAT_TIME,            /* granulepos */
313 314 315 316 317 318 319 320 321 322 323
    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
324
static const GstEventMask *
325
gst_ogg_pad_event_masks (GstPad * pad)
326
{
327
  static const GstEventMask src_event_masks[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
328 329
    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH},
    {0,}
330
  };
331

332
  return src_event_masks;
333
}
334

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
335
static const GstQueryType *
336
gst_ogg_pad_query_types (GstPad * pad)
337
{
338
  static const GstQueryType query_types[] = {
339
    GST_QUERY_POSITION,
340
    GST_QUERY_TOTAL,
341 342
    0
  };
343

344
  return query_types;
345 346
}

347 348
static GstCaps *
gst_ogg_pad_getcaps (GstPad * pad)
349
{
350
  return gst_caps_ref (GST_PAD_CAPS (pad));
351 352
}

353 354 355 356
static gboolean
gst_ogg_pad_src_convert (GstPad * pad,
    GstFormat src_format, gint64 src_value,
    GstFormat * dest_format, gint64 * dest_value)
357
{
358 359
  gboolean res = FALSE;
  GstOggDemux *ogg;
360

361
  ogg = GST_OGG_DEMUX (GST_PAD_PARENT (pad));
362

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

365
  return res;
366 367
}

368
static gboolean
369
gst_ogg_pad_src_query (GstPad * pad, GstQueryType type,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
370
    GstFormat * format, gint64 * value)
371
{
372
  gboolean res = TRUE;
373 374 375
  GstOggDemux *ogg;
  GstOggPad *cur;

376 377
  ogg = GST_OGG_DEMUX (GST_PAD_PARENT (pad));
  cur = GST_OGG_PAD (pad);
378

379 380
  switch (type) {
    case GST_QUERY_POSITION:
381 382 383 384
      *value = cur->current_granule;
      break;
    case GST_QUERY_TOTAL:
      *value = ogg->total_time;
385 386
      break;
    default:
387
      res = FALSE;
388 389
      break;
  }
390

391 392 393 394
  return res;
}

static gboolean
395
gst_ogg_pad_event (GstPad * pad, GstEvent * event)
396
{
397
  gboolean res;
398
  GstOggDemux *ogg;
399
  GstOggPad *cur;
400

401 402
  ogg = GST_OGG_DEMUX (GST_PAD_PARENT (pad));
  cur = GST_OGG_PAD (pad);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
403

404 405
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
406
    {
407
      gint64 offset;
408

409 410 411 412
      /* can't seek if we are not seekable */
      if (!ogg->seekable) {
        res = FALSE;
        goto done_unref;
413
      }
414 415 416 417
      /* we can only seek on time */
      if (GST_EVENT_SEEK_FORMAT (event) != GST_FORMAT_TIME) {
        res = FALSE;
        goto done_unref;
418
      }
419
      offset = GST_EVENT_SEEK_OFFSET (event);
420
      gst_event_unref (event);
421 422 423 424

      /* now do the seek */
      res = gst_ogg_demux_perform_seek (ogg, offset);
      break;
425
    }
426
    default:
427 428
      res = gst_pad_event_default (pad, event);
      break;
429 430
  }

431
  return res;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
432

433
done_unref:
434
  gst_event_unref (event);
435 436 437
  return res;
}

438
static void
439
gst_ogg_pad_reset (GstOggPad * pad)
440
{
441 442
  ogg_stream_reset (&pad->stream);
  /* FIXME: need a discont here */
443 444
}

445 446
/* the filter function for selecting the elements we can use in
 *  * autoplugging */
447
static gboolean
448
gst_ogg_demux_factory_filter (GstPluginFeature * feature, GstCaps * caps)
449
{
450 451
  guint rank;
  const gchar *klass;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
452

453 454 455 456 457 458 459 460 461
  /* 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;
462
  }
463

464 465 466 467
  /* only select elements with autoplugging rank */
  rank = gst_plugin_feature_get_rank (feature);
  if (rank < GST_RANK_MARGINAL)
    return FALSE;
468

469 470 471 472 473 474 475 476
  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 */
477
    templates = gst_element_factory_get_static_pad_templates (factory);
478 479

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

482 483 484
      /* we only care about the sink templates */
      if (templ->direction == GST_PAD_SINK) {
        GstCaps *intersect;
485

486
        /* try to intersect the caps with the caps of the template */
487 488
        intersect = gst_caps_intersect (caps,
            gst_static_caps_get (&templ->static_caps));
489 490 491 492 493 494 495 496

        /* 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);
497
      }
498
    }
499
  }
500
  return FALSE;
501

502 503
found:
  return TRUE;
504
}
505

506 507 508
/* function used to sort element features */
static gint
compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2)
509
{
510
  gint diff;
511

512 513 514 515 516
  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));
517 518
}

519 520
static GstFlowReturn
gst_ogg_pad_internal_chain (GstPad * pad, GstBuffer * buffer)
521
{
522 523
  GstOggPad *oggpad;
  GstClockTime timestamp;
524

525
  oggpad = gst_pad_get_element_private (pad);
526

527 528 529
  timestamp = GST_BUFFER_TIMESTAMP (buffer);
  GST_DEBUG_OBJECT (oggpad, "received buffer from iternal pad, TS=%lld",
      timestamp);
530

531 532
  if (oggpad->start_time == -1)
    oggpad->start_time = timestamp;
533

534
  return GST_FLOW_OK;
535 536
}

537 538 539 540 541 542 543 544 545
/* 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)
546
{
547 548 549
  GstCaps *caps;
  GstElement *element = NULL;
  GstOggDemux *ogg = pad->ogg;
550

551 552
  if (GST_PAD_CAPS (pad) != NULL)
    return TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
553

554
  caps = gst_ogg_type_find (packet);
555

556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
  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);
587 588 589
          pad->elem_out =
              gst_pad_new_from_template (gst_static_pad_template_get
              (&internaltemplate), "internal");
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
          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;
606

607 608 609 610
  gst_pad_set_caps (GST_PAD (pad), caps);
  gst_caps_unref (caps);

  return TRUE;
611 612
}

613 614 615 616 617 618
/* 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)
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 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
  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);
    }
685
  } else {
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
    /* 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
706 707 708
#if 0
done:
#endif
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 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850
  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;
851
  }
852
  return NULL;
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 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
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);

908
static void
909
gst_ogg_demux_base_init (gpointer g_class)
910
{
911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
  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;
934 935
}

936
static void
937
gst_ogg_demux_init (GstOggDemux * ogg)
938
{
939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967
  /* 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_formats_function (ogg->sinkpad, gst_ogg_pad_formats);
  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);
968 969 970
}

static gboolean
971 972 973
gst_ogg_demux_handle_event (GstPad * pad, GstEvent * event)
{
  GstOggDemux *ogg = GST_OGG_DEMUX (GST_PAD_PARENT (pad));
974

975 976 977 978 979 980 981 982 983
  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);
  }
984 985 986
  return TRUE;
}

987 988 989
/* submit the given buffer to the ogg sync.
 *
 * Returns the number of bytes submited.
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
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;

1033
  GST_LOG_OBJECT (ogg, "get data %lld %lld", ogg->offset, ogg->length);
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 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
  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;
}

1147
static gboolean
1148
gst_ogg_demux_deactivate_current_chain (GstOggDemux * ogg)
1149
{
1150 1151
  gint i;
  GstOggChain *chain = ogg->current_chain;
1152

1153 1154 1155 1156 1157 1158 1159
  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));
1160
  }