gstoggmux.c 48 KB
Newer Older
Wim Taymans's avatar
Wim Taymans committed
1
2
/* OGG muxer plugin for GStreamer
 * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
3
 * Copyright (C) 2006 Thomas Vander Stichele <thomas at apestaart dot org>
Wim Taymans's avatar
Wim Taymans committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>
Wim Taymans's avatar
Wim Taymans committed
26
27
#include <gst/base/gstcollectpads.h>

Wim Taymans's avatar
Wim Taymans committed
28
#include <ogg/ogg.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
29
/* memcpy - if someone knows a way to get rid of it, please speak up
Wim Taymans's avatar
Wim Taymans committed
30
31
32
 * note: the ogg docs even say you need this... */
#include <string.h>
#include <time.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
33
#include <stdlib.h>             /* rand, srand, atoi */
Wim Taymans's avatar
Wim Taymans committed
34
35
36
37
38
39
40
41

GST_DEBUG_CATEGORY_STATIC (gst_ogg_mux_debug);
#define GST_CAT_DEFAULT gst_ogg_mux_debug

#define GST_TYPE_OGG_MUX (gst_ogg_mux_get_type())
#define GST_OGG_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_MUX, GstOggMux))
#define GST_OGG_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_MUX, GstOggMux))
#define GST_IS_OGG_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_MUX))
42
#define GST_IS_OGG_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_MUX))
Wim Taymans's avatar
Wim Taymans committed
43

44
45
46
47
48
49
50
51
/* This isn't generally what you'd want with an end-time macro, because
   technically the end time of a buffer with invalid duration is invalid. But
   for sorting ogg pages this is what we want. */
#define GST_BUFFER_END_TIME(buf) \
    (GST_BUFFER_DURATION_IS_VALID (buf) \
    ? GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf) \
    : GST_BUFFER_TIMESTAMP (buf))

52
53
#define GST_GP_FORMAT "[gp %8" G_GINT64_FORMAT "]"

Wim Taymans's avatar
Wim Taymans committed
54
55
56
typedef struct _GstOggMux GstOggMux;
typedef struct _GstOggMuxClass GstOggMuxClass;

Wim Taymans's avatar
Wim Taymans committed
57
58
59
60
61
62
63
typedef enum
{
  GST_OGG_PAD_STATE_CONTROL = 0,
  GST_OGG_PAD_STATE_DATA = 1
}
GstOggPadState;

Wim Taymans's avatar
Wim Taymans committed
64
65
66
/* all information needed for one ogg stream */
typedef struct
{
Wim Taymans's avatar
Wim Taymans committed
67
  GstCollectData collect;       /* we extend the CollectData */
Wim Taymans's avatar
Wim Taymans committed
68
69
70
71
72
73
74

  GstBuffer *buffer;            /* the queued buffer for this pad */

  gint serial;
  ogg_stream_state stream;
  gint64 packetno;              /* number of next packet */
  gint64 pageno;                /* number of next page */
75
  guint64 duration;             /* duration of current page */
Wim Taymans's avatar
Wim Taymans committed
76
  gboolean eos;
77
  gint64 offset;
78
79
80
81
82
83
84
  GstClockTime timestamp;       /* timestamp of the first packet on the next
                                 * page to be dequeued */
  GstClockTime timestamp_end;   /* end timestamp of last complete packet on
                                   the next page to be dequeued */
  GstClockTime gp_time;         /* time corresponding to the gp value of the
                                   last complete packet on the next page to be
                                   dequeued */
Wim Taymans's avatar
Wim Taymans committed
85

Wim Taymans's avatar
Wim Taymans committed
86
  GstOggPadState state;         /* state of the pad */
87
88

  GList *headers;
89

90
91
  GQueue *pagebuffers;          /* List of pages in buffers ready for pushing */

92
93
94
  gboolean new_page;            /* starting a new page */
  gboolean first_delta;         /* was the first packet in the page a delta */
  gboolean prev_delta;          /* was the previous buffer a delta frame */
Wim Taymans's avatar
Wim Taymans committed
95
96
97
98
99
100
101
}
GstOggPad;

struct _GstOggMux
{
  GstElement element;

Wim Taymans's avatar
Wim Taymans committed
102
  /* source pad */
Wim Taymans's avatar
Wim Taymans committed
103
104
  GstPad *srcpad;

Wim Taymans's avatar
Wim Taymans committed
105
106
  /* sinkpads */
  GstCollectPads *collect;
Wim Taymans's avatar
Wim Taymans committed
107

Wim Taymans's avatar
Wim Taymans committed
108
  /* the pad we are currently using to fill a page */
Wim Taymans's avatar
Wim Taymans committed
109
110
111
112
113
  GstOggPad *pulling;

  /* next timestamp for the page */
  GstClockTime next_ts;

114
115
116
  /* Last timestamp actually output on src pad */
  GstClockTime last_ts;

Wim Taymans's avatar
Wim Taymans committed
117
118
  /* offset in stream */
  guint64 offset;
119
120
121

  /* need_headers */
  gboolean need_headers;
122
123

  guint64 max_delay;
124
  guint64 max_page_delay;
125

126
  GstOggPad *delta_pad;         /* when a delta frame is detected on a stream, we mark
127
128
                                   pages as delta frames up to the page that has the
                                   keyframe */
129

Wim Taymans's avatar
Wim Taymans committed
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
};

typedef enum
{
  GST_OGG_FLAG_BOS = GST_ELEMENT_FLAG_LAST,
  GST_OGG_FLAG_EOS
}
GstOggFlag;

struct _GstOggMuxClass
{
  GstElementClass parent_class;
};

/* elementfactory information */
Stefan Kost's avatar
Stefan Kost committed
145
146
static const GstElementDetails gst_ogg_mux_details =
GST_ELEMENT_DETAILS ("Ogg muxer",
Wim Taymans's avatar
Wim Taymans committed
147
148
149
150
151
152
153
154
155
156
157
    "Codec/Muxer",
    "mux ogg streams (info about ogg: http://xiph.org)",
    "Wim Taymans <wim@fluendo.com>");

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

Wim Taymans's avatar
Wim Taymans committed
158
/* set to 0.5 seconds by default */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
159
160
#define DEFAULT_MAX_DELAY       G_GINT64_CONSTANT(500000000)
#define DEFAULT_MAX_PAGE_DELAY  G_GINT64_CONSTANT(500000000)
Wim Taymans's avatar
Wim Taymans committed
161
162
enum
{
163
164
  ARG_0,
  ARG_MAX_DELAY,
165
  ARG_MAX_PAGE_DELAY,
Wim Taymans's avatar
Wim Taymans committed
166
167
168
169
170
171
172
173
174
175
176
};

static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/ogg")
    );

static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
177
178
    GST_STATIC_CAPS ("video/x-theora; "
        "audio/x-vorbis; audio/x-flac; audio/x-speex; "
179
        "application/x-ogm-video; application/x-ogm-audio; video/x-dirac; "
180
        "video/x-smoke; text/x-cmml, encoded = (boolean) TRUE")
Wim Taymans's avatar
Wim Taymans committed
181
182
183
184
185
    );

static void gst_ogg_mux_base_init (gpointer g_class);
static void gst_ogg_mux_class_init (GstOggMuxClass * klass);
static void gst_ogg_mux_init (GstOggMux * ogg_mux);
186
static void gst_ogg_mux_finalize (GObject * object);
Wim Taymans's avatar
Wim Taymans committed
187

Wim Taymans's avatar
Wim Taymans committed
188
189
static GstFlowReturn
gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
190
191
192
static gboolean gst_ogg_mux_handle_src_event (GstPad * pad, GstEvent * event);
static GstPad *gst_ogg_mux_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * name);
193
194
static void gst_ogg_mux_release_pad (GstElement * element, GstPad * pad);

Wim Taymans's avatar
Wim Taymans committed
195
196
197
198
static void gst_ogg_mux_set_property (GObject * object,
    guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_ogg_mux_get_property (GObject * object,
    guint prop_id, GValue * value, GParamSpec * pspec);
199
200
static GstStateChangeReturn gst_ogg_mux_change_state (GstElement * element,
    GstStateChange transition);
Wim Taymans's avatar
Wim Taymans committed
201
202
203
204
205
206
207
208
209
210

static GstElementClass *parent_class = NULL;

/*static guint gst_ogg_mux_signals[LAST_SIGNAL] = { 0 }; */

GType
gst_ogg_mux_get_type (void)
{
  static GType ogg_mux_type = 0;

211
  if (G_UNLIKELY (ogg_mux_type == 0)) {
Wim Taymans's avatar
Wim Taymans committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
    static const GTypeInfo ogg_mux_info = {
      sizeof (GstOggMuxClass),
      gst_ogg_mux_base_init,
      NULL,
      (GClassInitFunc) gst_ogg_mux_class_init,
      NULL,
      NULL,
      sizeof (GstOggMux),
      0,
      (GInstanceInitFunc) gst_ogg_mux_init,
    };

    ogg_mux_type =
        g_type_register_static (GST_TYPE_ELEMENT, "GstOggMux", &ogg_mux_info,
        0);
  }
  return ogg_mux_type;
}

static void
gst_ogg_mux_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&src_factory));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&sink_factory));

  gst_element_class_set_details (element_class, &gst_ogg_mux_details);
}

static void
gst_ogg_mux_class_init (GstOggMuxClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;

253
  parent_class = g_type_class_peek_parent (klass);
Wim Taymans's avatar
Wim Taymans committed
254

255
  gobject_class->finalize = gst_ogg_mux_finalize;
256
257
258
  gobject_class->get_property = gst_ogg_mux_get_property;
  gobject_class->set_property = gst_ogg_mux_set_property;

Wim Taymans's avatar
Wim Taymans committed
259
  gstelement_class->request_new_pad = gst_ogg_mux_request_new_pad;
260
  gstelement_class->release_pad = gst_ogg_mux_release_pad;
Wim Taymans's avatar
Wim Taymans committed
261

262
263
264
265
  g_object_class_install_property (gobject_class, ARG_MAX_DELAY,
      g_param_spec_uint64 ("max-delay", "Max delay",
          "Maximum delay in multiplexing streams", 0, G_MAXUINT64,
          DEFAULT_MAX_DELAY, (GParamFlags) G_PARAM_READWRITE));
266
267
268
269
  g_object_class_install_property (gobject_class, ARG_MAX_PAGE_DELAY,
      g_param_spec_uint64 ("max-page-delay", "Max page delay",
          "Maximum delay for sending out a page", 0, G_MAXUINT64,
          DEFAULT_MAX_PAGE_DELAY, (GParamFlags) G_PARAM_READWRITE));
270

Wim Taymans's avatar
Wim Taymans committed
271
272
273
274
  gstelement_class->change_state = gst_ogg_mux_change_state;

}

Wim Taymans's avatar
Wim Taymans committed
275
#if 0
Wim Taymans's avatar
Wim Taymans committed
276
277
278
279
280
static const GstEventMask *
gst_ogg_mux_get_sink_event_masks (GstPad * pad)
{
  static const GstEventMask gst_ogg_mux_sink_event_masks[] = {
    {GST_EVENT_EOS, 0},
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
281
    {GST_EVENT_DISCONTINUOUS, 0},
Wim Taymans's avatar
Wim Taymans committed
282
283
284
285
286
    {0,}
  };

  return gst_ogg_mux_sink_event_masks;
}
Wim Taymans's avatar
Wim Taymans committed
287
#endif
Wim Taymans's avatar
Wim Taymans committed
288

289
290
291
292
293
294
295
296
static void
gst_ogg_mux_clear (GstOggMux * ogg_mux)
{
  ogg_mux->pulling = NULL;
  ogg_mux->need_headers = TRUE;
  ogg_mux->max_delay = DEFAULT_MAX_DELAY;
  ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;
  ogg_mux->delta_pad = NULL;
297
298
  ogg_mux->offset = 0;
  ogg_mux->next_ts = 0;
299
  ogg_mux->last_ts = GST_CLOCK_TIME_NONE;
300
301
}

Wim Taymans's avatar
Wim Taymans committed
302
303
304
305
306
307
308
309
310
311
312
static void
gst_ogg_mux_init (GstOggMux * ogg_mux)
{
  GstElementClass *klass = GST_ELEMENT_GET_CLASS (ogg_mux);

  ogg_mux->srcpad =
      gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
          "src"), "src");
  gst_pad_set_event_function (ogg_mux->srcpad, gst_ogg_mux_handle_src_event);
  gst_element_add_pad (GST_ELEMENT (ogg_mux), ogg_mux->srcpad);

313
  GST_OBJECT_FLAG_SET (GST_ELEMENT (ogg_mux), GST_OGG_FLAG_BOS);
Wim Taymans's avatar
Wim Taymans committed
314
315
316
317

  /* seed random number generator for creation of serial numbers */
  srand (time (NULL));

318
319
  ogg_mux->collect = gst_collect_pads_new ();
  gst_collect_pads_set_function (ogg_mux->collect,
320
321
      (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_ogg_mux_collected),
      ogg_mux);
322

323
  gst_ogg_mux_clear (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
324
325
}

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
static void
gst_ogg_mux_finalize (GObject * object)
{
  GstOggMux *ogg_mux;

  ogg_mux = GST_OGG_MUX (object);

  if (ogg_mux->collect) {
    gst_object_unref (ogg_mux->collect);
    ogg_mux->collect = NULL;
  }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

Wim Taymans's avatar
Wim Taymans committed
341
static GstPadLinkReturn
342
gst_ogg_mux_sinkconnect (GstPad * pad, GstPad * peer)
Wim Taymans's avatar
Wim Taymans committed
343
344
345
346
347
{
  GstOggMux *ogg_mux;

  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));

348
  GST_DEBUG_OBJECT (ogg_mux, "sinkconnect triggered on %s", GST_PAD_NAME (pad));
349

350
  gst_object_unref (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
351
352
353
354
355
356
357
358
359
360

  return GST_PAD_LINK_OK;
}

static GstPad *
gst_ogg_mux_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * req_name)
{
  GstOggMux *ogg_mux;
  GstPad *newpad;
Wim Taymans's avatar
Wim Taymans committed
361
  GstElementClass *klass;
Wim Taymans's avatar
Wim Taymans committed
362
363
364

  g_return_val_if_fail (templ != NULL, NULL);

Wim Taymans's avatar
Wim Taymans committed
365
366
  if (templ->direction != GST_PAD_SINK)
    goto wrong_direction;
Wim Taymans's avatar
Wim Taymans committed
367
368
369
370

  g_return_val_if_fail (GST_IS_OGG_MUX (element), NULL);
  ogg_mux = GST_OGG_MUX (element);

Wim Taymans's avatar
Wim Taymans committed
371
372
373
374
375
376
  klass = GST_ELEMENT_GET_CLASS (element);

  if (templ != gst_element_class_get_pad_template (klass, "sink_%d"))
    goto wrong_template;

  {
Wim Taymans's avatar
Wim Taymans committed
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
    gint serial;
    gchar *name;

    if (req_name == NULL || strlen (req_name) < 6) {
      /* no name given when requesting the pad, use random serial number */
      serial = rand ();
    } else {
      /* parse serial number from requested padname */
      serial = atoi (&req_name[5]);
    }
    /* create new pad with the name */
    name = g_strdup_printf ("sink_%d", serial);
    newpad = gst_pad_new_from_template (templ, name);
    g_free (name);

    /* construct our own wrapper data structure for the pad to
     * keep track of its status */
    {
Wim Taymans's avatar
Wim Taymans committed
395
396
397
      GstOggPad *oggpad;

      oggpad = (GstOggPad *)
398
          gst_collect_pads_add_pad (ogg_mux->collect, newpad,
Wim Taymans's avatar
Wim Taymans committed
399
          sizeof (GstOggPad));
Wim Taymans's avatar
Wim Taymans committed
400
401
402
403
404
405
406
407

      oggpad->serial = serial;
      ogg_stream_init (&oggpad->stream, serial);
      oggpad->packetno = 0;
      oggpad->pageno = 0;
      oggpad->eos = FALSE;
      /* we assume there will be some control data first for this pad */
      oggpad->state = GST_OGG_PAD_STATE_CONTROL;
408
409
410
      oggpad->new_page = TRUE;
      oggpad->first_delta = FALSE;
      oggpad->prev_delta = FALSE;
411
      oggpad->pagebuffers = g_queue_new ();
Wim Taymans's avatar
Wim Taymans committed
412
413
414
415
416
417
418
419
420
    }
  }

  /* setup some pad functions */
  gst_pad_set_link_function (newpad, gst_ogg_mux_sinkconnect);
  /* dd the pad to the element */
  gst_element_add_pad (element, newpad);

  return newpad;
Wim Taymans's avatar
Wim Taymans committed
421
422
423
424
425
426
427
428
429
430
431
432

  /* ERRORS */
wrong_direction:
  {
    g_warning ("ogg_mux: request pad that is not a SINK pad\n");
    return NULL;
  }
wrong_template:
  {
    g_warning ("ogg_mux: this is not our template!\n");
    return NULL;
  }
Wim Taymans's avatar
Wim Taymans committed
433
434
}

435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
static void
gst_ogg_mux_release_pad (GstElement * element, GstPad * pad)
{
  GstOggMux *ogg_mux;
  GSList *walk;

  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));

  /* FIXME: When a request pad is released while paused or playing, 
   * we probably need to do something to finalise its stream in the
   * ogg data we're producing, but I'm not sure what */

  /* Find out GstOggPad in the collect pads info and clean it up */

  GST_OBJECT_LOCK (ogg_mux->collect);
450
  for (walk = ogg_mux->collect->data; walk; walk = g_slist_next (walk)) {
451
452
453
454
455
    GstOggPad *oggpad = (GstOggPad *) walk->data;
    GstCollectData *cdata = (GstCollectData *) walk->data;
    GstBuffer *buf;

    if (cdata->pad == pad) {
456
      ogg_stream_clear (&oggpad->stream);
457
458
459
460
461
462

      while ((buf = g_queue_pop_head (oggpad->pagebuffers)) != NULL) {
        gst_buffer_unref (buf);
      }

      g_queue_free (oggpad->pagebuffers);
463
      oggpad->pagebuffers = NULL;
464
465
466
467
468
469
470
    }
  }
  GST_OBJECT_UNLOCK (ogg_mux->collect);

  gst_collect_pads_remove_pad (ogg_mux->collect, pad);
}

Wim Taymans's avatar
Wim Taymans committed
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
/* handle events */
static gboolean
gst_ogg_mux_handle_src_event (GstPad * pad, GstEvent * event)
{
  GstEventType type;

  type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;

  switch (type) {
    case GST_EVENT_SEEK:
      /* disable seeking for now */
      return FALSE;
    default:
      break;
  }

  return gst_pad_event_default (pad, event);
}

490
static GstBuffer *
491
gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
Wim Taymans's avatar
Wim Taymans committed
492
493
494
495
{
  GstBuffer *buffer;

  /* allocate space for header and body */
Wim Taymans's avatar
Wim Taymans committed
496
  buffer = gst_buffer_new_and_alloc (page->header_len + page->body_len);
Wim Taymans's avatar
Wim Taymans committed
497
498
499
500
  memcpy (GST_BUFFER_DATA (buffer), page->header, page->header_len);
  memcpy (GST_BUFFER_DATA (buffer) + page->header_len,
      page->body, page->body_len);

501
502
503
504
  /* Here we set granulepos as our OFFSET_END to give easy direct access to
   * this value later. Before we push it, we reset this to OFFSET + SIZE
   * (see gst_ogg_mux_push_buffer). */
  GST_BUFFER_OFFSET_END (buffer) = ogg_page_granulepos (page);
505
  if (delta)
506
    GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
Wim Taymans's avatar
Wim Taymans committed
507

508
  GST_LOG_OBJECT (mux, GST_GP_FORMAT
509
      " created buffer %p from ogg page", ogg_page_granulepos (page), buffer);
510

511
512
513
  return buffer;
}

Wim Taymans's avatar
Wim Taymans committed
514
static GstFlowReturn
515
gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer)
516
{
517
518
  GstCaps *caps;

519
520
521
522
  /* fix up OFFSET and OFFSET_END again */
  GST_BUFFER_OFFSET (buffer) = mux->offset;
  mux->offset += GST_BUFFER_SIZE (buffer);
  GST_BUFFER_OFFSET_END (buffer) = mux->offset;
523

524
525
526
527
528
529
530
531
  /* Ensure we have monotonically increasing timestamps in the output. */
  if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
    if (GST_BUFFER_TIMESTAMP (buffer) < mux->last_ts)
      GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts;
    else
      mux->last_ts = GST_BUFFER_TIMESTAMP (buffer);
  }

532
533
534
535
  caps = gst_pad_get_negotiated_caps (mux->srcpad);
  gst_buffer_set_caps (buffer, caps);
  gst_caps_unref (caps);

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
  return gst_pad_push (mux->srcpad, buffer);
}

/* if all queues have at least one page, dequeue the page with the lowest
 * timestamp */
static gboolean
gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret)
{
  GSList *walk;
  GstOggPad *opad = NULL;       /* "oldest" pad */
  GstClockTime oldest = GST_CLOCK_TIME_NONE;
  GstBuffer *buf = NULL;
  gboolean ret = FALSE;

  *flowret = GST_FLOW_OK;

  walk = mux->collect->data;
  while (walk) {
    GstOggPad *pad = (GstOggPad *) walk->data;

    /* We need each queue to either be at EOS, or have one or more pages
     * available with a set granulepos (i.e. not -1), otherwise we don't have
     * enough data yet to determine which stream needs to go next for correct
     * time ordering. */
    if (pad->pagebuffers->length == 0) {
      if (pad->eos) {
562
563
        GST_LOG_OBJECT (pad->collect.pad,
            "pad is EOS, skipping for dequeue decision");
564
      } else {
565
566
        GST_LOG_OBJECT (pad->collect.pad,
            "no pages in this queue, can't dequeue");
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
        return FALSE;
      }
    } else {
      /* We then need to check for a non-negative granulepos */
      int i;
      gboolean valid = FALSE;

      for (i = 0; i < pad->pagebuffers->length; i++) {
        buf = g_queue_peek_nth (pad->pagebuffers, i);
        /* Here we check the OFFSET_END, which is actually temporarily the
         * granulepos value for this buffer */
        if (GST_BUFFER_OFFSET_END (buf) != -1) {
          valid = TRUE;
          break;
        }
      }
      if (!valid) {
584
585
        GST_LOG_OBJECT (pad->collect.pad,
            "No page timestamps in queue, can't dequeue");
586
587
588
589
590
591
592
593
594
595
596
        return FALSE;
      }
    }

    walk = g_slist_next (walk);
  }

  walk = mux->collect->data;
  while (walk) {
    GstOggPad *pad = (GstOggPad *) walk->data;

597
    /* any page with a granulepos of -1 can be pushed immediately.
598
599
600
     * TODO: it CAN be, but it seems silly to do so? */
    buf = g_queue_peek_head (pad->pagebuffers);
    while (buf && GST_BUFFER_OFFSET_END (buf) == -1) {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
601
      GST_LOG_OBJECT (pad->collect.pad, "[gp        -1] pushing page");
602
603
604
605
606
607
608
609
610
      g_queue_pop_head (pad->pagebuffers);
      *flowret = gst_ogg_mux_push_buffer (mux, buf);
      buf = g_queue_peek_head (pad->pagebuffers);
      ret = TRUE;
    }

    if (buf) {
      /* if no oldest buffer yet, take this one */
      if (oldest == GST_CLOCK_TIME_NONE) {
611
612
613
614
        GST_LOG_OBJECT (mux, "no oldest yet, taking buffer %p from pad %"
            GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT,
            buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
        oldest = GST_BUFFER_OFFSET (buf);
615
616
617
        opad = pad;
      } else {
        /* if we have an oldest, compare with this one */
618
619
620
621
622
        if (GST_BUFFER_OFFSET (buf) < oldest) {
          GST_LOG_OBJECT (mux, "older buffer %p, taking from pad %"
              GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT,
              buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
          oldest = GST_BUFFER_OFFSET (buf);
623
624
625
626
627
628
629
630
631
632
          opad = pad;
        }
      }
    }
    walk = g_slist_next (walk);
  }

  if (oldest != GST_CLOCK_TIME_NONE) {
    g_assert (opad);
    buf = g_queue_pop_head (opad->pagebuffers);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
633
    GST_LOG_OBJECT (opad->collect.pad,
634
635
636
        GST_GP_FORMAT " pushing oldest page buffer %p (granulepos time %"
        GST_TIME_FORMAT ")", GST_BUFFER_OFFSET_END (buf), buf,
        GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
637
638
639
640
641
642
643
    *flowret = gst_ogg_mux_push_buffer (mux, buf);
    ret = TRUE;
  }

  return ret;
}

644
645
646
647
648
649
650
651
652
653
654
655
656
/* put the given ogg page on a per-pad queue, timestamping it correctly.
 * after that, dequeue and push as many pages as possible.
 * Caller should make sure:
 * pad->timestamp     was set with the timestamp of the first packet put
 *                    on the page
 * pad->timestamp_end was set with the timestamp + duration of the last packet
 *                    put on the page
 * pad->gp_time       was set with the time matching the gp of the last
 *                    packet put on the page
 *
 * will also reset timestamp and timestamp_end, so caller func can restart
 * counting.
 */
657
658
659
660
static GstFlowReturn
gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPad * pad, ogg_page * page,
    gboolean delta)
{
Wim Taymans's avatar
Wim Taymans committed
661
  GstFlowReturn ret;
662
  GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
663

664
  /* take the timestamp of the first packet on this page */
665
  GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp;
666
  GST_BUFFER_DURATION (buffer) = pad->timestamp_end - pad->timestamp;
667
668
  /* take the gp time of the last completed packet on this page */
  GST_BUFFER_OFFSET (buffer) = pad->gp_time;
669

670
  /* the next page will start where the current page's end time leaves off */
671
672
  pad->timestamp = pad->timestamp_end;

673
  g_queue_push_tail (pad->pagebuffers, buffer);
674
675
676
677
678
  GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
      " queued buffer page %p (gp time %"
      GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT
      "), %d page buffers queued", ogg_page_granulepos (page),
      buffer, GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)),
679
680
      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
      g_queue_get_length (pad->pagebuffers));
Wim Taymans's avatar
Wim Taymans committed
681

682
683
684
685
  while (gst_ogg_mux_dequeue_page (mux, &ret)) {
    if (ret != GST_FLOW_OK)
      break;
  }
Wim Taymans's avatar
Wim Taymans committed
686
687

  return ret;
Wim Taymans's avatar
Wim Taymans committed
688
689
690
}

/*
691
692
693
694
695
696
697
 * Given two pads, compare the buffers queued on it.
 * Returns:
 *  0 if they have an equal priority
 * -1 if the first is better
 *  1 if the second is better
 * Priority decided by: a) validity, b) older timestamp, c) smaller number
 * of muxed pages
Wim Taymans's avatar
Wim Taymans committed
698
699
 */
static gint
700
701
gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPad * first,
    GstOggPad * second)
Wim Taymans's avatar
Wim Taymans committed
702
{
703
  guint64 firsttime, secondtime;
Wim Taymans's avatar
Wim Taymans committed
704

705
706
707
  /* if the first pad doesn't contain anything or is even NULL, return
   * the second pad as best candidate and vice versa */
  if (first == NULL || first->buffer == NULL)
Wim Taymans's avatar
Wim Taymans committed
708
    return 1;
709
  if (second == NULL || second->buffer == NULL)
Wim Taymans's avatar
Wim Taymans committed
710
711
    return -1;

712
713
714
  /* no timestamp on first buffer, it must go first */
  firsttime = GST_BUFFER_TIMESTAMP (first->buffer);
  if (firsttime == GST_CLOCK_TIME_NONE)
Wim Taymans's avatar
Wim Taymans committed
715
716
    return -1;

717
718
719
  /* no timestamp on second buffer, it must go first */
  secondtime = GST_BUFFER_TIMESTAMP (second->buffer);
  if (secondtime == GST_CLOCK_TIME_NONE)
Wim Taymans's avatar
Wim Taymans committed
720
721
    return 1;

722
723
  /* first buffer has higher timestamp, second one should go first */
  if (secondtime < firsttime)
Wim Taymans's avatar
Wim Taymans committed
724
    return 1;
725
726
  /* second buffer has higher timestamp, first one should go first */
  else if (secondtime > firsttime)
Wim Taymans's avatar
Wim Taymans committed
727
728
729
730
    return -1;
  else {
    /* buffers with equal timestamps, prefer the pad that has the
     * least number of pages muxed */
731
    if (second->pageno < first->pageno)
Wim Taymans's avatar
Wim Taymans committed
732
      return 1;
733
    else if (second->pageno > first->pageno)
Wim Taymans's avatar
Wim Taymans committed
734
735
736
737
738
739
740
741
      return -1;
  }

  /* same priority if all of the above failed */
  return 0;
}

/* make sure a buffer is queued on all pads, returns a pointer to an oggpad
742
743
 * that holds the best buffer or NULL when no pad was usable.
 * "best" means the buffer marked with the lowest timestamp */
Wim Taymans's avatar
Wim Taymans committed
744
static GstOggPad *
Wim Taymans's avatar
Wim Taymans committed
745
gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
Wim Taymans's avatar
Wim Taymans committed
746
{
747
  GstOggPad *bestpad = NULL, *still_hungry = NULL;
Wim Taymans's avatar
Wim Taymans committed
748
749
750
  GSList *walk;

  /* try to make sure we have a buffer from each usable pad first */
Wim Taymans's avatar
Wim Taymans committed
751
  walk = ogg_mux->collect->data;
Wim Taymans's avatar
Wim Taymans committed
752
  while (walk) {
Wim Taymans's avatar
Wim Taymans committed
753
754
    GstOggPad *pad;
    GstCollectData *data;
Wim Taymans's avatar
Wim Taymans committed
755

Wim Taymans's avatar
Wim Taymans committed
756
757
    data = (GstCollectData *) walk->data;
    pad = (GstOggPad *) data;
Wim Taymans's avatar
Wim Taymans committed
758

Wim Taymans's avatar
Wim Taymans committed
759
760
    walk = g_slist_next (walk);

761
    GST_LOG_OBJECT (data->pad, "looking at pad for buffer");
762

Wim Taymans's avatar
Wim Taymans committed
763
    /* try to get a new buffer for this pad if needed and possible */
Wim Taymans's avatar
Wim Taymans committed
764
765
766
    if (pad->buffer == NULL) {
      GstBuffer *buf;
      gboolean incaps;
Wim Taymans's avatar
Wim Taymans committed
767

768
      buf = gst_collect_pads_pop (ogg_mux->collect, data);
769
      GST_LOG_OBJECT (data->pad, "popped buffer %" GST_PTR_FORMAT, buf);
Wim Taymans's avatar
Wim Taymans committed
770

771
772
773
774
775
776
777
      /* On EOS we get a NULL buffer */
      if (buf != NULL) {
        incaps = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
        /* if we need headers */
        if (pad->state == GST_OGG_PAD_STATE_CONTROL) {
          /* and we have one */
          if (incaps) {
778
779
            GST_DEBUG_OBJECT (ogg_mux,
                "got incaps buffer in control state, ignoring");
780
781
782
783
            /* just ignore */
            gst_buffer_unref (buf);
            buf = NULL;
          } else {
784
785
            GST_DEBUG_OBJECT (ogg_mux,
                "got data buffer in control state, switching " "to data mode");
786
787
788
            /* this is a data buffer so switch to data state */
            pad->state = GST_OGG_PAD_STATE_DATA;
          }
Wim Taymans's avatar
Wim Taymans committed
789
        }
790
      } else {
791
        GST_DEBUG_OBJECT (data->pad, "EOS on pad");
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
        if (!pad->eos) {
          ogg_page page;
          GstFlowReturn ret;

          /* Just gone to EOS. Flush existing page(s) */
          pad->eos = TRUE;

          while (ogg_stream_flush (&pad->stream, &page)) {
            /* Place page into the per-pad queue */
            ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
                pad->first_delta);
            /* increment the page number counter */
            pad->pageno++;
            /* mark other pages as delta */
            pad->first_delta = TRUE;
          }
        }
Wim Taymans's avatar
Wim Taymans committed
809
      }
810

Wim Taymans's avatar
Wim Taymans committed
811
812
      pad->buffer = buf;
    }
Wim Taymans's avatar
Wim Taymans committed
813
814
815

    /* we should have a buffer now, see if it is the best pad to
     * pull on */
816
    if (pad->buffer) {
817
      if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) {
818
        GST_LOG_OBJECT (data->pad, "new best pad");
819

Wim Taymans's avatar
Wim Taymans committed
820
        bestpad = pad;
821
      }
822
    } else if (!pad->eos) {
823
      GST_LOG_OBJECT (data->pad, "hungry pad");
824
      still_hungry = pad;
Wim Taymans's avatar
Wim Taymans committed
825
826
    }
  }
827
828
829
830
831
832

  if (still_hungry)
    /* drop back into collectpads... */
    return still_hungry;
  else
    return bestpad;
Wim Taymans's avatar
Wim Taymans committed
833
834
}

835
836
837
838
839
840
static GList *
gst_ogg_mux_get_headers (GstOggPad * pad)
{
  GList *res = NULL;
  GstOggMux *ogg_mux;
  GstStructure *structure;
841
  GstCaps *caps;
Wim Taymans's avatar
Wim Taymans committed
842
  GstPad *thepad;
843

Wim Taymans's avatar
Wim Taymans committed
844
  thepad = pad->collect.pad;
845

Wim Taymans's avatar
Wim Taymans committed
846
  ogg_mux = GST_OGG_MUX (GST_PAD_PARENT (thepad));
847

848
  GST_LOG_OBJECT (thepad, "getting headers");
Wim Taymans's avatar
Wim Taymans committed
849
850

  caps = gst_pad_get_negotiated_caps (thepad);
851
852
853
854
855
856
  if (caps != NULL) {
    const GValue *streamheader;

    structure = gst_caps_get_structure (caps, 0);
    streamheader = gst_structure_get_value (structure, "streamheader");
    if (streamheader != NULL) {
857
      GST_LOG_OBJECT (thepad, "got header");
858
      if (G_VALUE_TYPE (streamheader) == GST_TYPE_ARRAY) {
859
860
861
        GArray *bufarr = g_value_peek_pointer (streamheader);
        gint i;

862
        GST_LOG_OBJECT (thepad, "got fixed list");
Wim Taymans's avatar
Wim Taymans committed
863

864
865
866
        for (i = 0; i < bufarr->len; i++) {
          GValue *bufval = &g_array_index (bufarr, GValue, i);

867
          GST_LOG_OBJECT (thepad, "item %d", i);
868
869
870
          if (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER) {
            GstBuffer *buf = g_value_peek_pointer (bufval);

871
            GST_LOG_OBJECT (thepad, "adding item %d to header list", i);
Wim Taymans's avatar
Wim Taymans committed
872

Wim Taymans's avatar
Wim Taymans committed
873
            gst_buffer_ref (buf);
874
875
876
            res = g_list_append (res, buf);
          }
        }
Wim Taymans's avatar
Wim Taymans committed
877
      } else {
878
        GST_LOG_OBJECT (thepad, "streamheader is not fixed list");
879
      }
Wim Taymans's avatar
Wim Taymans committed
880
    } else {
881
      GST_LOG_OBJECT (thepad, "caps done have streamheader");
882
    }
883
    gst_caps_unref (caps);
Wim Taymans's avatar
Wim Taymans committed
884
  } else {
885
    GST_LOG_OBJECT (thepad, "got empty caps as negotiated format");
886
887
888
889
  }
  return res;
}

Wim Taymans's avatar
Wim Taymans committed
890
static GstCaps *
891
892
gst_ogg_mux_set_header_on_caps (GstCaps * caps, GList * buffers)
{
Wim Taymans's avatar
Wim Taymans committed
893
  GstStructure *structure;
894
  GValue array = { 0 };
895
896
  GList *walk = buffers;

Wim Taymans's avatar
Wim Taymans committed
897
898
899
900
  caps = gst_caps_make_writable (caps);

  structure = gst_caps_get_structure (caps, 0);

901
  /* put buffers in a fixed list */
902
  g_value_init (&array, GST_TYPE_ARRAY);
903
904
905

  while (walk) {
    GstBuffer *buf = GST_BUFFER (walk->data);
906
    GstBuffer *copy;
907
908
909
910
911
    GValue value = { 0 };

    walk = walk->next;

    /* mark buffer */
912
    GST_LOG ("Setting IN_CAPS on buffer of length %d", GST_BUFFER_SIZE (buf));
913
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
914
915

    g_value_init (&value, GST_TYPE_BUFFER);
916
917
918
    copy = gst_buffer_copy (buf);
    gst_value_set_buffer (&value, copy);
    gst_buffer_unref (copy);
919
    gst_value_array_append_value (&array, &value);
920
921
    g_value_unset (&value);
  }
922
923
  gst_structure_set_value (structure, "streamheader", &array);
  g_value_unset (&array);
Wim Taymans's avatar
Wim Taymans committed
924
925

  return caps;
926
927
}

928
/*
929
930
931
932
933
934
935
 * For each pad we need to write out one (small) header in one
 * page that allows decoders to identify the type of the stream.
 * After that we need to write out all extra info for the decoders.
 * In the case of a codec that also needs data as configuration, we can
 * find that info in the streamcaps. 
 * After writing the headers we must start a new page for the data.
 */
Wim Taymans's avatar
Wim Taymans committed
936
static GstFlowReturn
937
938
939
940
941
gst_ogg_mux_send_headers (GstOggMux * mux)
{
  GSList *walk;
  GList *hbufs, *hwalk;
  GstCaps *caps;
Wim Taymans's avatar
Wim Taymans committed
942
  GstFlowReturn ret;
943
944

  hbufs = NULL;
Wim Taymans's avatar
Wim Taymans committed
945
  ret = GST_FLOW_OK;
946

947
  GST_LOG_OBJECT (mux, "collecting headers");
948

Wim Taymans's avatar
Wim Taymans committed
949
  walk = mux->collect->data;
950
  while (walk) {
Wim Taymans's avatar
Wim Taymans committed
951
952
    GstOggPad *pad;
    GstPad *thepad;
953

Wim Taymans's avatar
Wim Taymans committed
954
955
    pad = (GstOggPad *) walk->data;
    thepad = pad->collect.pad;
956

Wim Taymans's avatar
Wim Taymans committed
957
958
    walk = g_slist_next (walk);

959
    GST_LOG_OBJECT (mux, "looking at pad %s:%s", GST_DEBUG_PAD_NAME (thepad));
960
961
962
963
964
965
966
967
968

    /* if the pad has no buffer, we don't care */
    if (pad->buffer == NULL)
      continue;

    /* now figure out the headers */
    pad->headers = gst_ogg_mux_get_headers (pad);
  }

969
  GST_LOG_OBJECT (mux, "creating BOS pages");
Wim Taymans's avatar
Wim Taymans committed
970
  walk = mux->collect->data;
971
  while (walk) {
Wim Taymans's avatar
Wim Taymans committed
972
    GstOggPad *pad;
973
974
975
    GstBuffer *buf;
    ogg_packet packet;
    ogg_page page;
Wim Taymans's avatar
Wim Taymans committed
976
    GstPad *thepad;
977
978
979
    GstCaps *caps;
    GstStructure *structure;
    GstBuffer *hbuf;
Wim Taymans's avatar
Wim Taymans committed
980
981
982

    pad = (GstOggPad *) walk->data;
    thepad = pad->collect.pad;
983
984
    caps = gst_pad_get_negotiated_caps (thepad);
    structure = gst_caps_get_structure (caps, 0);
985
986
987
988
989

    walk = walk->next;

    pad->packetno = 0;

990
    GST_LOG_OBJECT (thepad, "looping over headers");
991
992
993
994

    if (pad->headers) {
      buf = GST_BUFFER (pad->headers->data);
      pad->headers = g_list_remove (pad->headers, buf);
995
    } else if (pad->buffer) {
996
997
      buf = pad->buffer;
      gst_buffer_ref (buf);
998
    } else {
999
      /* fixme -- should be caught in the previous list traversal. */
1000
      GST_OBJECT_LOCK (pad);
For faster browsing, not all history is shown. View entire blame