gstoggmux.c 55.2 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
#define GST_BUFFER_RUNNING_TIME(buf, oggpad) \
    (GST_BUFFER_DURATION_IS_VALID (buf) \
67
    ? gst_segment_to_running_time (&(oggpad)->segment, GST_FORMAT_TIME, \
68
69
    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
300
301

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

Wim Taymans's avatar
Wim Taymans committed
302
static GstPadLinkReturn
303
gst_ogg_mux_sinkconnect (GstPad * pad, GstPad * peer)
Wim Taymans's avatar
Wim Taymans committed
304
305
306
307
308
{
  GstOggMux *ogg_mux;

  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));

309
  GST_DEBUG_OBJECT (ogg_mux, "sinkconnect triggered on %s", GST_PAD_NAME (pad));
310

311
  gst_object_unref (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
312
313
314
315

  return GST_PAD_LINK_OK;
}

316
317
318
319
static gboolean
gst_ogg_mux_sink_event (GstPad * pad, GstEvent * event)
{
  GstOggMux *ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));
320
  GstOggPadData *ogg_pad = (GstOggPadData *) gst_pad_get_element_private (pad);
321
322
323
324
325
326
  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)) {
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
    case GST_EVENT_NEWSEGMENT:{
      gboolean update;
      gdouble rate;
      gdouble applied_rate;
      GstFormat format;
      gint64 start, stop, position;

      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;
      }
342
      gst_segment_set_newsegment_full (&ogg_pad->segment, update, rate,
343
344
          applied_rate, format, start, stop, position);

345
      break;
346
    }
347
    case GST_EVENT_FLUSH_STOP:{
348
      gst_segment_init (&ogg_pad->segment, GST_FORMAT_TIME);
349
350
      break;
    }
351
352
353
354
355
356
357
358
359
360
361
362
363
    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;
}

364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
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
392
393
394
395
396
397
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
398
  GstElementClass *klass;
Wim Taymans's avatar
Wim Taymans committed
399
400
401

  g_return_val_if_fail (templ != NULL, NULL);

Wim Taymans's avatar
Wim Taymans committed
402
403
  if (templ->direction != GST_PAD_SINK)
    goto wrong_direction;
Wim Taymans's avatar
Wim Taymans committed
404
405
406
407

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

Wim Taymans's avatar
Wim Taymans committed
408
409
410
411
412
413
  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
414
415
416
417
418
    gint serial;
    gchar *name;

    if (req_name == NULL || strlen (req_name) < 6) {
      /* no name given when requesting the pad, use random serial number */
419
      serial = gst_ogg_mux_generate_serialno (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
420
421
422
423
424
    } 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
425
    GST_DEBUG_OBJECT (ogg_mux, "Creating new pad for serial %d", serial);
Wim Taymans's avatar
Wim Taymans committed
426
427
428
429
430
431
432
    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 */
    {
433
      GstOggPadData *oggpad;
Wim Taymans's avatar
Wim Taymans committed
434

435
      oggpad = (GstOggPadData *)
436
          gst_collect_pads_add_pad_full (ogg_mux->collect, newpad,
437
          sizeof (GstOggPadData), gst_ogg_mux_ogg_pad_destroy_notify);
438
      ogg_mux->active_pads++;
Wim Taymans's avatar
Wim Taymans committed
439

440
441
      oggpad->map.serialno = serial;
      ogg_stream_init (&oggpad->map.stream, oggpad->map.serialno);
Wim Taymans's avatar
Wim Taymans committed
442
443
444
445
446
      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;
447
448
449
      oggpad->new_page = TRUE;
      oggpad->first_delta = FALSE;
      oggpad->prev_delta = FALSE;
450
      oggpad->data_pushed = FALSE;
451
      oggpad->pagebuffers = g_queue_new ();
452
453
      oggpad->map.headers = NULL;
      oggpad->map.queued = NULL;
454
455

      gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
456
457
458
459

      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
460
461
462
463
464
    }
  }

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

Wim Taymans's avatar
Wim Taymans committed
466
467
468
469
  /* dd the pad to the element */
  gst_element_add_pad (element, newpad);

  return newpad;
Wim Taymans's avatar
Wim Taymans committed
470
471
472
473
474
475
476
477
478
479
480
481

  /* 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
482
483
}

484
485
486
487
488
489
490
static void
gst_ogg_mux_release_pad (GstElement * element, GstPad * pad)
{
  GstOggMux *ogg_mux;

  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));

491
  gst_collect_pads_remove_pad (ogg_mux->collect, pad);
492
  gst_element_remove_pad (element, pad);
493
494

  gst_object_unref (ogg_mux);
495
496
}

Wim Taymans's avatar
Wim Taymans committed
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
/* 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);
}

516
static GstBuffer *
517
gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
Wim Taymans's avatar
Wim Taymans committed
518
519
520
521
{
  GstBuffer *buffer;

  /* allocate space for header and body */
Wim Taymans's avatar
Wim Taymans committed
522
  buffer = gst_buffer_new_and_alloc (page->header_len + page->body_len);
Wim Taymans's avatar
Wim Taymans committed
523
524
525
526
  memcpy (GST_BUFFER_DATA (buffer), page->header, page->header_len);
  memcpy (GST_BUFFER_DATA (buffer) + page->header_len,
      page->body, page->body_len);

527
528
529
530
  /* 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);
531
  if (delta)
532
    GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
Wim Taymans's avatar
Wim Taymans committed
533

534
  GST_LOG_OBJECT (mux, GST_GP_FORMAT
535
536
      " created buffer %p from ogg page",
      GST_GP_CAST (ogg_page_granulepos (page)), buffer);
537

538
539
540
  return buffer;
}

Wim Taymans's avatar
Wim Taymans committed
541
static GstFlowReturn
542
543
gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer,
    GstOggPadData * oggpad)
544
{
545
546
  GstCaps *caps;

547
548
549
550
  /* 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;
551

552
553
  /* Ensure we have monotonically increasing timestamps in the output. */
  if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
554
555
    gint64 run_time = GST_BUFFER_RUNNING_TIME (buffer, oggpad);
    if (mux->last_ts != GST_CLOCK_TIME_NONE && run_time < mux->last_ts)
556
557
      GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts;
    else
558
      mux->last_ts = run_time;
559
560
  }

561
  caps = gst_pad_get_negotiated_caps (mux->srcpad);
562
  gst_buffer_set_caps (buffer, caps);
563
564
  if (caps)
    gst_caps_unref (caps);
565

566
567
568
569
570
571
572
573
574
  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;
575
  GstOggPadData *opad = NULL;   /* "oldest" pad */
576
577
578
579
580
581
582
583
  GstClockTime oldest = GST_CLOCK_TIME_NONE;
  GstBuffer *buf = NULL;
  gboolean ret = FALSE;

  *flowret = GST_FLOW_OK;

  walk = mux->collect->data;
  while (walk) {
584
    GstOggPadData *pad = (GstOggPadData *) walk->data;
585
586
587
588
589
590
591

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

    walk = g_slist_next (walk);
  }

  walk = mux->collect->data;
  while (walk) {
625
    GstOggPadData *pad = (GstOggPadData *) walk->data;
626

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

  return ret;
}

674
675
676
677
678
679
680
681
682
683
684
685
686
/* 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.
 */
687
static GstFlowReturn
688
689
gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPadData * pad,
    ogg_page * page, gboolean delta)
690
{
Wim Taymans's avatar
Wim Taymans committed
691
  GstFlowReturn ret;
692
  GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
693

694
  /* take the timestamp of the first packet on this page */
695
  GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp;
696
  GST_BUFFER_DURATION (buffer) = pad->timestamp_end - pad->timestamp;
697
698
  /* take the gp time of the last completed packet on this page */
  GST_BUFFER_OFFSET (buffer) = pad->gp_time;
699

700
  /* the next page will start where the current page's end time leaves off */
701
702
  pad->timestamp = pad->timestamp_end;

703
  g_queue_push_tail (pad->pagebuffers, buffer);
704
705
706
  GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
      " queued buffer page %p (gp time %"
      GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT
707
      "), %d page buffers queued", GST_GP_CAST (ogg_page_granulepos (page)),
708
      buffer, GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)),
709
710
      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
      g_queue_get_length (pad->pagebuffers));
Wim Taymans's avatar
Wim Taymans committed
711

712
713
714
715
  while (gst_ogg_mux_dequeue_page (mux, &ret)) {
    if (ret != GST_FLOW_OK)
      break;
  }
Wim Taymans's avatar
Wim Taymans committed
716
717

  return ret;
Wim Taymans's avatar
Wim Taymans committed
718
719
720
}

/*
721
722
723
724
725
726
727
 * 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
728
729
 */
static gint
730
731
gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPadData * first,
    GstOggPadData * second)
Wim Taymans's avatar
Wim Taymans committed
732
{
733
  guint64 firsttime, secondtime;
Wim Taymans's avatar
Wim Taymans committed
734

735
736
  /* if the first pad doesn't contain anything or is even NULL, return
   * the second pad as best candidate and vice versa */
737
  if (first == NULL || (first->buffer == NULL && first->next_buffer == NULL))
Wim Taymans's avatar
Wim Taymans committed
738
    return 1;
739
  if (second == NULL || (second->buffer == NULL && second->next_buffer == NULL))
Wim Taymans's avatar
Wim Taymans committed
740
741
    return -1;

742
  /* no timestamp on first buffer, it must go first */
743
744
745
746
  if (first->buffer)
    firsttime = GST_BUFFER_TIMESTAMP (first->buffer);
  else
    firsttime = GST_BUFFER_TIMESTAMP (first->next_buffer);
747
  if (firsttime == GST_CLOCK_TIME_NONE)
Wim Taymans's avatar
Wim Taymans committed
748
749
    return -1;

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

758
  firsttime = gst_segment_to_running_time (&first->segment, GST_FORMAT_TIME,
759
      firsttime);
760
  secondtime = gst_segment_to_running_time (&second->segment, GST_FORMAT_TIME,
761
762
      secondtime);

763
764
  /* first buffer has higher timestamp, second one should go first */
  if (secondtime < firsttime)
Wim Taymans's avatar
Wim Taymans committed
765
    return 1;
766
767
  /* second buffer has higher timestamp, first one should go first */
  else if (secondtime > firsttime)
Wim Taymans's avatar
Wim Taymans committed
768
769
770
771
    return -1;
  else {
    /* buffers with equal timestamps, prefer the pad that has the
     * least number of pages muxed */
772
    if (second->pageno < first->pageno)
Wim Taymans's avatar
Wim Taymans committed
773
      return 1;
774
    else if (second->pageno > first->pageno)
Wim Taymans's avatar
Wim Taymans committed
775
776
777
778
779
780
781
      return -1;
  }

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

782
783
784
785
786
787
788
789
790
791
792
793
794
/* 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 */
795
static GstOggPadData *
Wim Taymans's avatar
Wim Taymans committed
796
gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
Wim Taymans's avatar
Wim Taymans committed
797
{
798
  GstOggPadData *bestpad = NULL, *still_hungry = NULL;
Wim Taymans's avatar
Wim Taymans committed
799
800
801
  GSList *walk;

  /* try to make sure we have a buffer from each usable pad first */
Wim Taymans's avatar
Wim Taymans committed
802
  walk = ogg_mux->collect->data;
Wim Taymans's avatar
Wim Taymans committed
803
  while (walk) {
804
    GstOggPadData *pad;
Wim Taymans's avatar
Wim Taymans committed
805
    GstCollectData *data;
Wim Taymans's avatar
Wim Taymans committed
806

Wim Taymans's avatar
Wim Taymans committed
807
    data = (GstCollectData *) walk->data;
808
    pad = (GstOggPadData *) data;
Wim Taymans's avatar
Wim Taymans committed
809

Wim Taymans's avatar
Wim Taymans committed
810
811
    walk = g_slist_next (walk);

812
    GST_LOG_OBJECT (data->pad, "looking at pad for buffer");
813

Wim Taymans's avatar
Wim Taymans committed
814
    /* try to get a new buffer for this pad if needed and possible */
Wim Taymans's avatar
Wim Taymans committed
815
816
    if (pad->buffer == NULL) {
      GstBuffer *buf;
Wim Taymans's avatar
Wim Taymans committed
817

818
819
820
821
822
823
824
825
      /* 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;
      }

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

829
830
      /* On EOS we get a NULL buffer */
      if (buf != NULL) {
831
832
833
834
        if (ogg_mux->delta_pad == NULL &&
            GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
          ogg_mux->delta_pad = pad;

835
836
837
        /* if we need headers */
        if (pad->state == GST_OGG_PAD_STATE_CONTROL) {
          /* and we have one */
838
          ogg_packet packet;
839
840
          gboolean is_header;

841
842
          packet.packet = GST_BUFFER_DATA (buf);
          packet.bytes = GST_BUFFER_SIZE (buf);
843
844
845
846

          if (GST_BUFFER_OFFSET_END_IS_VALID (buf))
            packet.granulepos = GST_BUFFER_OFFSET_END (buf);
          else
847
848
849
850
851
852
            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) {
853
854
855
856
857
              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);
858
859
860
            }
          }

861
862
863
864
          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);
865

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

886
887
888
          /* it's no longer active */
          ogg_mux->active_pads--;

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

892
          while (ogg_stream_flush (&pad->map.stream, &page)) {
893
894
895
896
897
898
899
900
901
            /* 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
902
      }
903

904
      pad->next_buffer = buf;
Wim Taymans's avatar
Wim Taymans committed
905
    }
Wim Taymans's avatar
Wim Taymans committed
906
907
908

    /* we should have a buffer now, see if it is the best pad to
     * pull on */
909
    if (pad->buffer || pad->next_buffer) {
910
      if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) {
911
912
913
        GST_LOG_OBJECT (data->pad,
            "new best pad, with buffers %" GST_PTR_FORMAT
            " and %" GST_PTR_FORMAT, pad->buffer, pad->next_buffer);
914

Wim Taymans's avatar
Wim Taymans committed
915
        bestpad = pad;
916
      }
917
    } else if (!pad->eos) {
918
      GST_LOG_OBJECT (data->pad, "hungry pad");
919
      still_hungry = pad;
Wim Taymans's avatar
Wim Taymans committed
920
921
    }
  }
922
923
924
925
926
927

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

930
static GList *
931
gst_ogg_mux_get_headers (GstOggPadData * pad)
932
933
934
{
  GList *res = NULL;
  GstStructure *structure;
935
  GstCaps *caps;
Wim Taymans's avatar
Wim Taymans committed
936
  GstPad *thepad;
937

Wim Taymans's avatar
Wim Taymans committed
938
  thepad = pad->collect.pad;
939

940
  GST_LOG_OBJECT (thepad, "getting headers");
Wim Taymans's avatar
Wim Taymans committed
941
942

  caps = gst_pad_get_negotiated_caps (thepad);
943
944
945
946
  if (caps != NULL) {
    const GValue *streamheader;

    structure = gst_caps_get_structure (caps, 0);
947
948
949
950
951
952
    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;
953

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

956
957
        for (i = 0; i < bufarr->len; i++) {
          GValue *bufval = &g_array_index (bufarr, GValue, i);
958

959
960
961
          GST_LOG_OBJECT (thepad, "item %d", i);
          if (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER) {
            GstBuffer *buf = g_value_peek_pointer (bufval);
962

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

965
966
            gst_buffer_ref (buf);
            res = g_list_append (res, buf);
967
968
          }
        }
Wim Taymans's avatar
Wim Taymans committed
969
      } else {
970
        GST_LOG_OBJECT (thepad, "streamheader is not fixed list");
971
      }
972
973
974
975

      /* Start a new page for every CMML buffer */
      if (gst_structure_has_name (structure, "text/x-cmml"))
        pad->always_flush_page = TRUE;
976
977
978
979
    } 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;
980
      pad->always_flush_page = TRUE;
981
982
    } else {
      GST_LOG_OBJECT (thepad, "caps don't have streamheader");
983
    }
984
    gst_caps_unref (caps);
Wim Taymans's avatar
Wim Taymans committed
985
  } else {
986
    GST_LOG_OBJECT (thepad, "got empty caps as negotiated format");
987
988
989
990
  }
  return res;
}

Wim Taymans's avatar
Wim Taymans committed
991
static GstCaps *
992
993
gst_ogg_mux_set_header_on_caps (GstCaps * caps, GList * buffers)
{
Wim Taymans's avatar
Wim Taymans committed
994
  GstStructure *structure;
995
  GValue array = { 0 };
996
997
  GList *walk = buffers;

Wim Taymans's avatar
Wim Taymans committed
998
999
1000
1001
  caps = gst_caps_make_writable (caps);

  structure = gst_caps_get_structure (caps, 0);

1002
  /* put buffers in a fixed list */
1003
  g_value_init (&array, GST_TYPE_ARRAY);
1004
1005
1006

  while (walk) {
    GstBuffer *buf = GST_BUFFER (walk->data);
1007
    GstBuffer *copy;
1008
1009
1010
1011
1012
    GValue value = { 0 };

    walk = walk->next;

    /* mark buffer */
1013
    GST_LOG ("Setting IN_CAPS on buffer of length %d", GST_BUFFER_SIZE (buf));
1014
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
1015
1016

    g_value_init (&value, GST_TYPE_BUFFER);
1017
1018
1019
    copy = gst_buffer_copy (buf);
    gst_value_set_buffer (&value, copy);
    gst_buffer_unref (copy);
1020
    gst_value_array_append_value (&array, &value);
1021
1022
    g_value_unset (&value);
  }
1023
1024
  gst_structure_set_value (structure, "streamheader", &array);
  g_value_unset (&array);
Wim Taymans's avatar
Wim Taymans committed
1025
1026

  return caps;
1027
1028
}

1029
/*
1030
1031
1032
1033
1034
1035
1036
 * 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
1037
static GstFlowReturn
1038
1039
1040
1041
1042
gst_ogg_mux_send_headers (GstOggMux * mux)
{
  GSList *walk;
  GList *hbufs, *hwalk;
  GstCaps *caps;
Wim Taymans's avatar
Wim Taymans committed
1043
  GstFlowReturn ret;
1044
1045

  hbufs = NULL;
Wim Taymans's avatar
Wim Taymans committed
1046
  ret = GST_FLOW_OK;
1047

1048
  GST_LOG_OBJECT (mux, "collecting headers");
1049

Wim Taymans's avatar
Wim Taymans committed
1050
  walk = mux->collect->data;
1051
  while (walk) {
1052
    GstOggPadData *pad;
Wim Taymans's avatar
Wim Taymans committed
1053
    GstPad *thepad;
1054

1055
    pad = (GstOggPadData *) walk->data;
Wim Taymans's avatar
Wim Taymans committed
1056
    thepad = pad->collect.pad;
1057

Wim Taymans's avatar
Wim Taymans committed
1058
1059
    walk = g_slist_next (walk);

1060
    GST_LOG_OBJECT (mux, "looking at pad %s:%s", GST_DEBUG_PAD_NAME (thepad));
1061
1062

    /* if the pad has no buffer, we don't care */
1063
    if (pad->buffer == NULL && pad->next_buffer == NULL)
1064
1065
1066
      continue;

    /* now figure out the headers */
1067
    pad->map.headers = gst_ogg_mux_get_headers (pad);
1068
1069
  }

1070
  GST_LOG_OBJECT (mux, "creating BOS pages");
Wim Taymans's avatar
Wim Taymans committed
1071
  walk = mux->collect->data;
1072
  while (walk) {
1073
    GstOggPadData *pad;
1074
1075
1076
    GstBuffer *buf;
    ogg_packet packet;
    ogg_page page;
Wim Taymans's avatar
Wim Taymans committed
1077
    GstPad *thepad;
1078
1079
1080
    GstCaps *caps;
    GstStructure *structure;
    GstBuffer *hbuf;
Wim Taymans's avatar
Wim Taymans committed
1081

1082
    pad = (GstOggPadData *) walk->data;
Wim Taymans's avatar
Wim Taymans committed
1083
    thepad = pad->collect.pad;
1084
1085
    caps = gst_pad_get_negotiated_caps (thepad);
    structure = gst_caps_get_structure (caps, 0);
1086
1087
1088
1089
1090

    walk = walk->next;

    pad->packetno = 0;