gstoggmux.c 55.5 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
 *
 * 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.
 */

21
22
/**
 * SECTION:element-oggmux
Stefan Kost's avatar
Stefan Kost committed
23
 * @see_also: <link linkend="gst-plugins-base-plugins-oggdemux">oggdemux</link>
24
25
 *
 * This element merges streams (audio and video) into ogg files.
26
27
 *
 * <refsect2>
28
 * <title>Example pipelines</title>
29
 * |[
30
 * gst-launch v4l2src num-buffers=500 ! video/x-raw-yuv,width=320,height=240 ! ffmpegcolorspace ! theoraenc ! oggmux ! filesink location=video.ogg
31
 * ]| Encodes a video stream captured from a v4l2-compatible camera to Ogg/Theora
32
 * (the encoding will stop automatically after 500 frames)
33
34
35
36
37
 * </refsect2>
 *
 * Last reviewed on 2008-02-06 (0.10.17)
 */

Wim Taymans's avatar
Wim Taymans committed
38
39
40
41
42
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>
Wim Taymans's avatar
Wim Taymans committed
43
#include <gst/base/gstcollectpads.h>
44
#include <gst/tag/tag.h>
Wim Taymans's avatar
Wim Taymans committed
45

46
47
#include "gstoggmux.h"

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
48
/* memcpy - if someone knows a way to get rid of it, please speak up
Wim Taymans's avatar
Wim Taymans committed
49
50
51
 * note: the ogg docs even say you need this... */
#include <string.h>
#include <time.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
52
#include <stdlib.h>             /* rand, srand, atoi */
Wim Taymans's avatar
Wim Taymans committed
53
54
55
56

GST_DEBUG_CATEGORY_STATIC (gst_ogg_mux_debug);
#define GST_CAT_DEFAULT gst_ogg_mux_debug

57
58
59
60
61
62
63
64
/* 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))

65
66
67
68
69
#define GST_BUFFER_RUNNING_TIME(buf, oggpad) \
    (GST_BUFFER_DURATION_IS_VALID (buf) \
    ? gst_segment_to_running_time ((oggpad)->segment, GST_FORMAT_TIME, \
    GST_BUFFER_TIMESTAMP (buf)) : 0)

70
#define GST_GP_FORMAT "[gp %8" G_GINT64_FORMAT "]"
71
#define GST_GP_CAST(_gp) ((gint64) _gp)
72

Wim Taymans's avatar
Wim Taymans committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
typedef enum
{
  GST_OGG_FLAG_BOS = GST_ELEMENT_FLAG_LAST,
  GST_OGG_FLAG_EOS
}
GstOggFlag;

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

Wim Taymans's avatar
Wim Taymans committed
87
/* set to 0.5 seconds by default */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
88
89
#define DEFAULT_MAX_DELAY       G_GINT64_CONSTANT(500000000)
#define DEFAULT_MAX_PAGE_DELAY  G_GINT64_CONSTANT(500000000)
Wim Taymans's avatar
Wim Taymans committed
90
91
enum
{
92
93
  ARG_0,
  ARG_MAX_DELAY,
94
  ARG_MAX_PAGE_DELAY,
Wim Taymans's avatar
Wim Taymans committed
95
96
97
98
99
100
101
102
103
104
105
};

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,
106
    GST_STATIC_CAPS ("video/x-theora; "
107
        "audio/x-vorbis; audio/x-flac; audio/x-speex; audio/x-celt; "
108
        "application/x-ogm-video; application/x-ogm-audio; video/x-dirac; "
109
        "video/x-smoke; video/x-vp8; text/x-cmml, encoded = (boolean) TRUE; "
110
        "subtitle/x-kate; application/x-kate")
Wim Taymans's avatar
Wim Taymans committed
111
112
113
114
115
    );

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);
116
static void gst_ogg_mux_finalize (GObject * object);
Wim Taymans's avatar
Wim Taymans committed
117

Wim Taymans's avatar
Wim Taymans committed
118
119
static GstFlowReturn
gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
120
121
122
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);
123
124
static void gst_ogg_mux_release_pad (GstElement * element, GstPad * pad);

Wim Taymans's avatar
Wim Taymans committed
125
126
127
128
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);
129
130
static GstStateChangeReturn gst_ogg_mux_change_state (GstElement * element,
    GstStateChange transition);
Wim Taymans's avatar
Wim Taymans committed
131
132
133
134
135
136
137
138
139
140

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;

141
  if (G_UNLIKELY (ogg_mux_type == 0)) {
Wim Taymans's avatar
Wim Taymans committed
142
143
144
145
146
147
148
149
150
151
152
    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,
    };
153
154
155
156
157
    static const GInterfaceInfo preset_info = {
      NULL,
      NULL,
      NULL
    };
Wim Taymans's avatar
Wim Taymans committed
158
159
160
161

    ogg_mux_type =
        g_type_register_static (GST_TYPE_ELEMENT, "GstOggMux", &ogg_mux_info,
        0);
162
163

    g_type_add_interface_static (ogg_mux_type, GST_TYPE_PRESET, &preset_info);
Wim Taymans's avatar
Wim Taymans committed
164
165
166
167
168
169
170
171
172
173
174
175
176
177
  }
  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));

178
179
180
181
  gst_element_class_set_details_simple (element_class,
      "Ogg muxer", "Codec/Muxer",
      "mux ogg streams (info about ogg: http://xiph.org)",
      "Wim Taymans <wim@fluendo.com>");
Wim Taymans's avatar
Wim Taymans committed
182
183
184
185
186
187
188
189
190
191
192
}

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

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

193
  parent_class = g_type_class_peek_parent (klass);
Wim Taymans's avatar
Wim Taymans committed
194

195
  gobject_class->finalize = gst_ogg_mux_finalize;
196
197
198
  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
199
  gstelement_class->request_new_pad = gst_ogg_mux_request_new_pad;
200
  gstelement_class->release_pad = gst_ogg_mux_release_pad;
Wim Taymans's avatar
Wim Taymans committed
201

202
203
204
  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,
205
206
          DEFAULT_MAX_DELAY,
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
207
208
209
  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,
210
211
          DEFAULT_MAX_PAGE_DELAY,
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
212

Wim Taymans's avatar
Wim Taymans committed
213
214
215
216
  gstelement_class->change_state = gst_ogg_mux_change_state;

}

Wim Taymans's avatar
Wim Taymans committed
217
#if 0
Wim Taymans's avatar
Wim Taymans committed
218
219
220
221
222
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
223
    {GST_EVENT_DISCONTINUOUS, 0},
Wim Taymans's avatar
Wim Taymans committed
224
225
226
227
228
    {0,}
  };

  return gst_ogg_mux_sink_event_masks;
}
Wim Taymans's avatar
Wim Taymans committed
229
#endif
Wim Taymans's avatar
Wim Taymans committed
230

231
232
233
234
235
236
static void
gst_ogg_mux_clear (GstOggMux * ogg_mux)
{
  ogg_mux->pulling = NULL;
  ogg_mux->need_headers = TRUE;
  ogg_mux->delta_pad = NULL;
237
238
  ogg_mux->offset = 0;
  ogg_mux->next_ts = 0;
239
  ogg_mux->last_ts = GST_CLOCK_TIME_NONE;
240
241
}

Wim Taymans's avatar
Wim Taymans committed
242
243
244
245
246
247
248
249
250
251
252
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);

253
  GST_OBJECT_FLAG_SET (GST_ELEMENT (ogg_mux), GST_OGG_FLAG_BOS);
Wim Taymans's avatar
Wim Taymans committed
254
255
256
257

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

258
259
  ogg_mux->collect = gst_collect_pads_new ();
  gst_collect_pads_set_function (ogg_mux->collect,
260
261
      (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_ogg_mux_collected),
      ogg_mux);
262

263
264
265
  ogg_mux->max_delay = DEFAULT_MAX_DELAY;
  ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;

266
  gst_ogg_mux_clear (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
267
268
}

269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
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);
}

284
285
286
static void
gst_ogg_mux_ogg_pad_destroy_notify (GstCollectData * data)
{
287
  GstOggPadData *oggpad = (GstOggPadData *) data;
288
289
  GstBuffer *buf;

290
  ogg_stream_clear (&oggpad->map.stream);
291
  gst_caps_replace (&oggpad->map.caps, NULL);
292
293
294
295
296
297
298
299

  if (oggpad->pagebuffers) {
    while ((buf = g_queue_pop_head (oggpad->pagebuffers)) != NULL) {
      gst_buffer_unref (buf);
    }
    g_queue_free (oggpad->pagebuffers);
    oggpad->pagebuffers = NULL;
  }
300
301
302
303
304

  if (oggpad->segment) {
    gst_segment_free (oggpad->segment);
    oggpad->segment = NULL;
  }
305
306
}

Wim Taymans's avatar
Wim Taymans committed
307
static GstPadLinkReturn
308
gst_ogg_mux_sinkconnect (GstPad * pad, GstPad * peer)
Wim Taymans's avatar
Wim Taymans committed
309
310
311
312
313
{
  GstOggMux *ogg_mux;

  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));

314
  GST_DEBUG_OBJECT (ogg_mux, "sinkconnect triggered on %s", GST_PAD_NAME (pad));
315

316
  gst_object_unref (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
317
318
319
320

  return GST_PAD_LINK_OK;
}

321
322
323
324
static gboolean
gst_ogg_mux_sink_event (GstPad * pad, GstEvent * event)
{
  GstOggMux *ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));
325
  GstOggPadData *ogg_pad = (GstOggPadData *) gst_pad_get_element_private (pad);
326
327
328
329
330
331
  gboolean ret;

  GST_DEBUG ("Got %s event on pad %s:%s", GST_EVENT_TYPE_NAME (event),
      GST_DEBUG_PAD_NAME (pad));

  switch (GST_EVENT_TYPE (event)) {
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
    case GST_EVENT_NEWSEGMENT:{
      gboolean update;
      gdouble rate;
      gdouble applied_rate;
      GstFormat format;
      gint64 start, stop, position;

      g_return_val_if_fail (ogg_pad->segment != NULL, FALSE);

      gst_event_parse_new_segment_full (event, &update, &rate,
          &applied_rate, &format, &start, &stop, &position);

      /* We don't support non time NEWSEGMENT events */
      if (format != GST_FORMAT_TIME) {
        gst_event_unref (event);
        return FALSE;
      }
      gst_segment_set_newsegment_full (ogg_pad->segment, update, rate,
          applied_rate, format, start, stop, position);

352
      break;
353
    }
354
355
356
357
    case GST_EVENT_FLUSH_STOP:{
      gst_segment_init (ogg_pad->segment, GST_FORMAT_UNDEFINED);
      break;
    }
358
359
360
361
362
363
364
365
366
367
368
369
370
    default:
      ret = TRUE;
      break;
  }

  /* now GstCollectPads can take care of the rest, e.g. EOS */
  if (ret)
    ret = ogg_pad->collect_event (pad, event);

  gst_object_unref (ogg_mux);
  return ret;
}

371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
static gboolean
gst_ogg_mux_is_serialno_present (GstOggMux * ogg_mux, guint32 serialno)
{
  GSList *walk;

  walk = ogg_mux->collect->data;
  while (walk) {
    GstOggPadData *pad = (GstOggPadData *) walk->data;
    if (pad->map.serialno == serialno)
      return TRUE;
    walk = walk->next;
  }

  return FALSE;
}

static guint32
gst_ogg_mux_generate_serialno (GstOggMux * ogg_mux)
{
  guint32 serialno;

  do {
    serialno = g_random_int_range (0, G_MAXINT32);
  } while (gst_ogg_mux_is_serialno_present (ogg_mux, serialno));

  return serialno;
}

Wim Taymans's avatar
Wim Taymans committed
399
400
401
402
403
404
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
405
  GstElementClass *klass;
Wim Taymans's avatar
Wim Taymans committed
406
407
408

  g_return_val_if_fail (templ != NULL, NULL);

Wim Taymans's avatar
Wim Taymans committed
409
410
  if (templ->direction != GST_PAD_SINK)
    goto wrong_direction;
Wim Taymans's avatar
Wim Taymans committed
411
412
413
414

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

Wim Taymans's avatar
Wim Taymans committed
415
416
417
418
419
420
  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
421
422
423
424
425
    gint serial;
    gchar *name;

    if (req_name == NULL || strlen (req_name) < 6) {
      /* no name given when requesting the pad, use random serial number */
426
      serial = gst_ogg_mux_generate_serialno (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
427
428
429
430
431
    } else {
      /* parse serial number from requested padname */
      serial = atoi (&req_name[5]);
    }
    /* create new pad with the name */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
432
    GST_DEBUG_OBJECT (ogg_mux, "Creating new pad for serial %d", serial);
Wim Taymans's avatar
Wim Taymans committed
433
434
435
436
437
438
439
    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 */
    {
440
      GstOggPadData *oggpad;
Wim Taymans's avatar
Wim Taymans committed
441

442
      oggpad = (GstOggPadData *)
443
          gst_collect_pads_add_pad_full (ogg_mux->collect, newpad,
444
          sizeof (GstOggPadData), gst_ogg_mux_ogg_pad_destroy_notify);
445
      ogg_mux->active_pads++;
Wim Taymans's avatar
Wim Taymans committed
446

447
448
      oggpad->map.serialno = serial;
      ogg_stream_init (&oggpad->map.stream, oggpad->map.serialno);
Wim Taymans's avatar
Wim Taymans committed
449
450
451
452
453
      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;
454
455
456
      oggpad->new_page = TRUE;
      oggpad->first_delta = FALSE;
      oggpad->prev_delta = FALSE;
457
      oggpad->data_pushed = FALSE;
458
      oggpad->pagebuffers = g_queue_new ();
459
460
      oggpad->map.headers = NULL;
      oggpad->map.queued = NULL;
461
462
463
      oggpad->segment = gst_segment_new ();
      gst_segment_set_newsegment (oggpad->segment, FALSE, 1, GST_FORMAT_TIME,
          0, -1, 0);
464
465
466
467

      oggpad->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
      gst_pad_set_event_function (newpad,
          GST_DEBUG_FUNCPTR (gst_ogg_mux_sink_event));
Wim Taymans's avatar
Wim Taymans committed
468
469
470
471
472
    }
  }

  /* setup some pad functions */
  gst_pad_set_link_function (newpad, gst_ogg_mux_sinkconnect);
473

Wim Taymans's avatar
Wim Taymans committed
474
475
476
477
  /* dd the pad to the element */
  gst_element_add_pad (element, newpad);

  return newpad;
Wim Taymans's avatar
Wim Taymans committed
478
479
480
481
482
483
484
485
486
487
488
489

  /* 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
490
491
}

492
493
494
495
496
497
498
static void
gst_ogg_mux_release_pad (GstElement * element, GstPad * pad)
{
  GstOggMux *ogg_mux;

  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));

499
  gst_collect_pads_remove_pad (ogg_mux->collect, pad);
500
  gst_element_remove_pad (element, pad);
501
502

  gst_object_unref (ogg_mux);
503
504
}

Wim Taymans's avatar
Wim Taymans committed
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
/* 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);
}

524
static GstBuffer *
525
gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
Wim Taymans's avatar
Wim Taymans committed
526
527
528
529
{
  GstBuffer *buffer;

  /* allocate space for header and body */
Wim Taymans's avatar
Wim Taymans committed
530
  buffer = gst_buffer_new_and_alloc (page->header_len + page->body_len);
Wim Taymans's avatar
Wim Taymans committed
531
532
533
534
  memcpy (GST_BUFFER_DATA (buffer), page->header, page->header_len);
  memcpy (GST_BUFFER_DATA (buffer) + page->header_len,
      page->body, page->body_len);

535
536
537
538
  /* 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);
539
  if (delta)
540
    GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
Wim Taymans's avatar
Wim Taymans committed
541

542
  GST_LOG_OBJECT (mux, GST_GP_FORMAT
543
544
      " created buffer %p from ogg page",
      GST_GP_CAST (ogg_page_granulepos (page)), buffer);
545

546
547
548
  return buffer;
}

Wim Taymans's avatar
Wim Taymans committed
549
static GstFlowReturn
550
551
gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer,
    GstOggPadData * oggpad)
552
{
553
554
  GstCaps *caps;

555
556
557
558
  /* 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;
559

560
561
  /* Ensure we have monotonically increasing timestamps in the output. */
  if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
562
563
    gint64 run_time = GST_BUFFER_RUNNING_TIME (buffer, oggpad);
    if (mux->last_ts != GST_CLOCK_TIME_NONE && run_time < mux->last_ts)
564
565
      GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts;
    else
566
      mux->last_ts = run_time;
567
568
  }

569
  caps = gst_pad_get_negotiated_caps (mux->srcpad);
570
  gst_buffer_set_caps (buffer, caps);
571
572
  if (caps)
    gst_caps_unref (caps);
573

574
575
576
577
578
579
580
581
582
  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;
583
  GstOggPadData *opad = NULL;   /* "oldest" pad */
584
585
586
587
588
589
590
591
  GstClockTime oldest = GST_CLOCK_TIME_NONE;
  GstBuffer *buf = NULL;
  gboolean ret = FALSE;

  *flowret = GST_FLOW_OK;

  walk = mux->collect->data;
  while (walk) {
592
    GstOggPadData *pad = (GstOggPadData *) walk->data;
593
594
595
596
597
598
599

    /* 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) {
600
601
        GST_LOG_OBJECT (pad->collect.pad,
            "pad is EOS, skipping for dequeue decision");
602
      } else {
603
604
        GST_LOG_OBJECT (pad->collect.pad,
            "no pages in this queue, can't dequeue");
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
        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) {
622
623
        GST_LOG_OBJECT (pad->collect.pad,
            "No page timestamps in queue, can't dequeue");
624
625
626
627
628
629
630
631
632
        return FALSE;
      }
    }

    walk = g_slist_next (walk);
  }

  walk = mux->collect->data;
  while (walk) {
633
    GstOggPadData *pad = (GstOggPadData *) walk->data;
634

635
    /* any page with a granulepos of -1 can be pushed immediately.
636
637
638
     * 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
639
      GST_LOG_OBJECT (pad->collect.pad, "[gp        -1] pushing page");
640
      g_queue_pop_head (pad->pagebuffers);
641
      *flowret = gst_ogg_mux_push_buffer (mux, buf, pad);
642
643
644
645
646
647
648
      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) {
649
650
651
652
        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);
653
654
655
        opad = pad;
      } else {
        /* if we have an oldest, compare with this one */
656
657
658
659
660
        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);
661
662
663
664
665
666
667
668
669
670
          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
671
    GST_LOG_OBJECT (opad->collect.pad,
672
673
674
        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)));
675
    *flowret = gst_ogg_mux_push_buffer (mux, buf, opad);
676
677
678
679
680
681
    ret = TRUE;
  }

  return ret;
}

682
683
684
685
686
687
688
689
690
691
692
693
694
/* 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.
 */
695
static GstFlowReturn
696
697
gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPadData * pad,
    ogg_page * page, gboolean delta)
698
{
Wim Taymans's avatar
Wim Taymans committed
699
  GstFlowReturn ret;
700
  GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
701

702
  /* take the timestamp of the first packet on this page */
703
  GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp;
704
  GST_BUFFER_DURATION (buffer) = pad->timestamp_end - pad->timestamp;
705
706
  /* take the gp time of the last completed packet on this page */
  GST_BUFFER_OFFSET (buffer) = pad->gp_time;
707

708
  /* the next page will start where the current page's end time leaves off */
709
710
  pad->timestamp = pad->timestamp_end;

711
  g_queue_push_tail (pad->pagebuffers, buffer);
712
713
714
  GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
      " queued buffer page %p (gp time %"
      GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT
715
      "), %d page buffers queued", GST_GP_CAST (ogg_page_granulepos (page)),
716
      buffer, GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)),
717
718
      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
      g_queue_get_length (pad->pagebuffers));
Wim Taymans's avatar
Wim Taymans committed
719

720
721
722
723
  while (gst_ogg_mux_dequeue_page (mux, &ret)) {
    if (ret != GST_FLOW_OK)
      break;
  }
Wim Taymans's avatar
Wim Taymans committed
724
725

  return ret;
Wim Taymans's avatar
Wim Taymans committed
726
727
728
}

/*
729
730
731
732
733
734
735
 * 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
736
737
 */
static gint
738
739
gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPadData * first,
    GstOggPadData * second)
Wim Taymans's avatar
Wim Taymans committed
740
{
741
  guint64 firsttime, secondtime;
Wim Taymans's avatar
Wim Taymans committed
742

743
744
  /* if the first pad doesn't contain anything or is even NULL, return
   * the second pad as best candidate and vice versa */
745
  if (first == NULL || (first->buffer == NULL && first->next_buffer == NULL))
Wim Taymans's avatar
Wim Taymans committed
746
    return 1;
747
  if (second == NULL || (second->buffer == NULL && second->next_buffer == NULL))
Wim Taymans's avatar
Wim Taymans committed
748
749
    return -1;

750
  /* no timestamp on first buffer, it must go first */
751
752
753
754
  if (first->buffer)
    firsttime = GST_BUFFER_TIMESTAMP (first->buffer);
  else
    firsttime = GST_BUFFER_TIMESTAMP (first->next_buffer);
755
  if (firsttime == GST_CLOCK_TIME_NONE)
Wim Taymans's avatar
Wim Taymans committed
756
757
    return -1;

758
  /* no timestamp on second buffer, it must go first */
759
760
761
762
  if (second->buffer)
    secondtime = GST_BUFFER_TIMESTAMP (second->buffer);
  else
    secondtime = GST_BUFFER_TIMESTAMP (second->next_buffer);
763
  if (secondtime == GST_CLOCK_TIME_NONE)
Wim Taymans's avatar
Wim Taymans committed
764
765
    return 1;

766
767
768
769
770
  firsttime = gst_segment_to_running_time (first->segment, GST_FORMAT_TIME,
      firsttime);
  secondtime = gst_segment_to_running_time (second->segment, GST_FORMAT_TIME,
      secondtime);

771
772
  /* first buffer has higher timestamp, second one should go first */
  if (secondtime < firsttime)
Wim Taymans's avatar
Wim Taymans committed
773
    return 1;
774
775
  /* second buffer has higher timestamp, first one should go first */
  else if (secondtime > firsttime)
Wim Taymans's avatar
Wim Taymans committed
776
777
778
779
    return -1;
  else {
    /* buffers with equal timestamps, prefer the pad that has the
     * least number of pages muxed */
780
    if (second->pageno < first->pageno)
Wim Taymans's avatar
Wim Taymans committed
781
      return 1;
782
    else if (second->pageno > first->pageno)
Wim Taymans's avatar
Wim Taymans committed
783
784
785
786
787
788
789
      return -1;
  }

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

790
791
792
793
794
795
796
797
798
799
800
801
802
/* make sure at least one buffer is queued on all pads, two if possible
 * 
 * if pad->buffer == NULL, pad->next_buffer !=  NULL, then
 *   we do not know if the buffer is the last or not
 * if pad->buffer != NULL, pad->next_buffer != NULL, then
 *   pad->buffer is not the last buffer for the pad
 * if pad->buffer != NULL, pad->next_buffer == NULL, then
 *   pad->buffer if the last buffer for the pad
 * 
 * returns a pointer to an oggpad that holds the best buffer, or
 * NULL when no pad was usable. "best" means the buffer marked
 * with the lowest timestamp. If best->buffer == NULL then nothing
 * should be done until more data arrives */
803
static GstOggPadData *
Wim Taymans's avatar
Wim Taymans committed
804
gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
Wim Taymans's avatar
Wim Taymans committed
805
{
806
  GstOggPadData *bestpad = NULL, *still_hungry = NULL;
Wim Taymans's avatar
Wim Taymans committed
807
808
809
  GSList *walk;

  /* try to make sure we have a buffer from each usable pad first */
Wim Taymans's avatar
Wim Taymans committed
810
  walk = ogg_mux->collect->data;
Wim Taymans's avatar
Wim Taymans committed
811
  while (walk) {
812
    GstOggPadData *pad;
Wim Taymans's avatar
Wim Taymans committed
813
    GstCollectData *data;
Wim Taymans's avatar
Wim Taymans committed
814

Wim Taymans's avatar
Wim Taymans committed
815
    data = (GstCollectData *) walk->data;
816
    pad = (GstOggPadData *) data;
Wim Taymans's avatar
Wim Taymans committed
817

Wim Taymans's avatar
Wim Taymans committed
818
819
    walk = g_slist_next (walk);

820
    GST_LOG_OBJECT (data->pad, "looking at pad for buffer");
821

Wim Taymans's avatar
Wim Taymans committed
822
    /* try to get a new buffer for this pad if needed and possible */
Wim Taymans's avatar
Wim Taymans committed
823
824
    if (pad->buffer == NULL) {
      GstBuffer *buf;
Wim Taymans's avatar
Wim Taymans committed
825

826
827
828
829
830
831
832
833
      /* shift the buffer along if needed (it's okay if next_buffer is NULL) */
      if (pad->buffer == NULL) {
        GST_LOG_OBJECT (data->pad, "shifting buffer %" GST_PTR_FORMAT,
            pad->next_buffer);
        pad->buffer = pad->next_buffer;
        pad->next_buffer = NULL;
      }

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

837
838
      /* On EOS we get a NULL buffer */
      if (buf != NULL) {
839
840
841
842
        if (ogg_mux->delta_pad == NULL &&
            GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
          ogg_mux->delta_pad = pad;

843
844
845
        /* if we need headers */
        if (pad->state == GST_OGG_PAD_STATE_CONTROL) {
          /* and we have one */
846
          ogg_packet packet;
847
848
          gboolean is_header;

849
850
          packet.packet = GST_BUFFER_DATA (buf);
          packet.bytes = GST_BUFFER_SIZE (buf);
851
852
853
854

          if (GST_BUFFER_OFFSET_END_IS_VALID (buf))
            packet.granulepos = GST_BUFFER_OFFSET_END (buf);
          else
855
856
857
858
859
860
            packet.granulepos = 0;

          /* if we're not yet in data mode, ensure we're setup on the first packet */
          if (!pad->have_type) {
            pad->have_type = gst_ogg_stream_setup_map (&pad->map, &packet);
            if (!pad->have_type) {
861
862
863
864
865
              GST_ERROR_OBJECT (pad, "mapper didn't recognise input stream "
                  "(pad caps: %" GST_PTR_FORMAT ")", GST_PAD_CAPS (pad));
            } else {
              GST_DEBUG_OBJECT (pad, "caps detected: %" GST_PTR_FORMAT,
                  pad->map.caps);
866
867
868
            }
          }

869
870
871
872
          if (pad->have_type)
            is_header = gst_ogg_stream_packet_is_header (&pad->map, &packet);
          else                  /* fallback (FIXME 0.11: remove IN_CAPS hack) */
            is_header = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
873

874
          if (is_header) {
875
            GST_DEBUG_OBJECT (ogg_mux,
876
                "got header buffer in control state, ignoring");
877
            /* just ignore */
878
            pad->map.n_header_packets_seen++;
879
880
881
            gst_buffer_unref (buf);
            buf = NULL;
          } else {
882
            GST_DEBUG_OBJECT (ogg_mux,
Vincent Penquerc'h's avatar
Vincent Penquerc'h committed
883
                "got data buffer in control state, switching to data mode");
884
885
886
            /* this is a data buffer so switch to data state */
            pad->state = GST_OGG_PAD_STATE_DATA;
          }
Wim Taymans's avatar
Wim Taymans committed
887
        }
888
      } else {
889
        GST_DEBUG_OBJECT (data->pad, "EOS on pad");
890
891
892
893
        if (!pad->eos) {
          ogg_page page;
          GstFlowReturn ret;

894
895
896
          /* it's no longer active */
          ogg_mux->active_pads--;

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

900
          while (ogg_stream_flush (&pad->map.stream, &page)) {
901
902
903
904
905
906
907
908
909
            /* 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
910
      }
911

912
      pad->next_buffer = buf;
Wim Taymans's avatar
Wim Taymans committed
913
    }
Wim Taymans's avatar
Wim Taymans committed
914
915
916

    /* we should have a buffer now, see if it is the best pad to
     * pull on */
917
    if (pad->buffer || pad->next_buffer) {
918
      if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) {
919
920
921
        GST_LOG_OBJECT (data->pad,
            "new best pad, with buffers %" GST_PTR_FORMAT
            " and %" GST_PTR_FORMAT, pad->buffer, pad->next_buffer);
922

Wim Taymans's avatar
Wim Taymans committed
923
        bestpad = pad;
924
      }
925
    } else if (!pad->eos) {
926
      GST_LOG_OBJECT (data->pad, "hungry pad");
927
      still_hungry = pad;
Wim Taymans's avatar
Wim Taymans committed
928
929
    }
  }
930
931
932
933
934
935

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

938
static GList *
939
gst_ogg_mux_get_headers (GstOggPadData * pad)
940
941
942
{
  GList *res = NULL;
  GstStructure *structure;
943
  GstCaps *caps;
Wim Taymans's avatar
Wim Taymans committed
944
  GstPad *thepad;
945

Wim Taymans's avatar
Wim Taymans committed
946
  thepad = pad->collect.pad;
947

948
  GST_LOG_OBJECT (thepad, "getting headers");
Wim Taymans's avatar
Wim Taymans committed
949
950

  caps = gst_pad_get_negotiated_caps (thepad);
951
952
953
954
  if (caps != NULL) {
    const GValue *streamheader;

    structure = gst_caps_get_structure (caps, 0);
955
956
957
958
959
960
    streamheader = gst_structure_get_value (structure, "streamheader");
    if (streamheader != NULL) {
      GST_LOG_OBJECT (thepad, "got header");
      if (G_VALUE_TYPE (streamheader) == GST_TYPE_ARRAY) {
        GArray *bufarr = g_value_peek_pointer (streamheader);
        gint i;
961

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

964
965
        for (i = 0; i < bufarr->len; i++) {
          GValue *bufval = &g_array_index (bufarr, GValue, i);
966

967
968
969
          GST_LOG_OBJECT (thepad, "item %d", i);
          if (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER) {
            GstBuffer *buf = g_value_peek_pointer (bufval);
970

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

973
974
            gst_buffer_ref (buf);
            res = g_list_append (res, buf);
975
976
          }
        }
Wim Taymans's avatar
Wim Taymans committed
977
      } else {
978
        GST_LOG_OBJECT (thepad, "streamheader is not fixed list");
979
      }
980
981
982
983

      /* Start a new page for every CMML buffer */
      if (gst_structure_has_name (structure, "text/x-cmml"))
        pad->always_flush_page = TRUE;
984
985
986
987
    } else if (gst_structure_has_name (structure, "video/x-dirac")) {
      res = g_list_append (res, pad->buffer);
      pad->buffer = pad->next_buffer;
      pad->next_buffer = NULL;
988
      pad->always_flush_page = TRUE;
989
990
    } else {
      GST_LOG_OBJECT (thepad, "caps don't have streamheader");
991
    }
992
    gst_caps_unref (caps);
Wim Taymans's avatar
Wim Taymans committed
993
  } else {
994
    GST_LOG_OBJECT (thepad, "got empty caps as negotiated format");
995
996
997
998
  }
  return res;
}

Wim Taymans's avatar
Wim Taymans committed
999
static GstCaps *
1000
1001
gst_ogg_mux_set_header_on_caps (GstCaps * caps, GList * buffers)
{
Wim Taymans's avatar
Wim Taymans committed
1002
  GstStructure *structure;
1003
  GValue array = { 0 };
1004
1005
  GList *walk = buffers;

Wim Taymans's avatar
Wim Taymans committed
1006
1007
1008
1009
  caps = gst_caps_make_writable (caps);

  structure = gst_caps_get_structure (caps, 0);

1010
  /* put buffers in a fixed list */
1011
  g_value_init (&array, GST_TYPE_ARRAY);
1012
1013
1014

  while (walk) {
    GstBuffer *buf = GST_BUFFER (walk->data);
1015
    GstBuffer *copy;
1016
1017
1018
1019
1020
    GValue value = { 0 };

    walk = walk->next;

    /* mark buffer */
1021
    GST_LOG ("Setting IN_CAPS on buffer of length %d", GST_BUFFER_SIZE (buf));
1022
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
1023
1024

    g_value_init (&value, GST_TYPE_BUFFER);
1025
1026
1027
    copy = gst_buffer_copy (buf);
    gst_value_set_buffer (&value, copy);
    gst_buffer_unref (copy);
1028
    gst_value_array_append_value (&array, &value);
1029
1030
    g_value_unset (&value);
  }
1031
1032
  gst_structure_set_value (structure, "streamheader", &array);
  g_value_unset (&array);
Wim Taymans's avatar
Wim Taymans committed
1033
1034

  return caps;
1035
1036
}

1037
/*
1038
1039
1040
1041
1042
1043
1044
 * 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
1045
static GstFlowReturn
1046
1047
1048
1049
1050
gst_ogg_mux_send_headers (GstOggMux * mux)
{
  GSList *walk;
  GList *hbufs, *hwalk;
  GstCaps *caps;
Wim Taymans's avatar
Wim Taymans committed
1051
  GstFlowReturn ret;
1052
1053

  hbufs = NULL;
Wim Taymans's avatar
Wim Taymans committed
1054
  ret = GST_FLOW_OK;
1055

1056
  GST_LOG_OBJECT (mux, "collecting headers");
1057

Wim Taymans's avatar
Wim Taymans committed
1058
  walk = mux->collect->data;
1059
  while (walk) {
1060
    GstOggPadData *pad;
Wim Taymans's avatar
Wim Taymans committed
1061
    GstPad *thepad;
1062

1063
    pad = (GstOggPadData *) walk->data;
Wim Taymans's avatar
Wim Taymans committed
1064
    thepad = pad->collect.pad;
1065

Wim Taymans's avatar
Wim Taymans committed
1066
1067
    walk = g_slist_next (walk);

1068
    GST_LOG_OBJECT (mux, "looking at pad %s:%s", GST_DEBUG_PAD_NAME (thepad));
1069
1070

    /* if the pad has no buffer, we don't care */
1071
    if (pad->buffer == NULL && pad->next_buffer == NULL)
1072
1073
1074
      continue;

    /* now figure out the headers */
1075
    pad->map.headers = gst_ogg_mux_get_headers (pad);
1076
1077
  }

1078
  GST_LOG_OBJECT (mux, "creating BOS pages");
Wim Taymans's avatar
Wim Taymans committed
1079
  walk = mux->collect->data;
1080
  while (walk) {
1081
    GstOggPadData *pad;
1082
1083
1084
    GstBuffer *buf;
    ogg_packet packet;
    ogg_page page;
Wim Taymans's avatar
Wim Taymans committed
1085
    GstPad *thepad;
1086
1087
1088
    GstCaps *caps;
    GstStructure *structure;
    GstBuffer *hbuf;
Wim Taymans's avatar
Wim Taymans committed
1089

1090
    pad = (GstOggPadData *) walk->data;
Wim Taymans's avatar
Wim Taymans committed
1091
    thepad = pad->collect.pad;
1092
1093
    caps = gst_pad_get_negotiated_caps (thepad);
    structure = gst_caps_get_structure (caps, 0);
1094
1095
1096
1097
1098

    walk = walk->next;

    pad->packetno = 0;

1099
    GST_LOG_OBJECT (thepad, "looping over headers");
1100

1101
1102
1103
    if (pad->map.headers) {
      buf = GST_BUFFER (pad->map.headers->data);
      pad->map.headers = g_list_remove (pad->map.headers, buf);
1104
    } else if (pad->buffer