gstoggmux.c 56.7 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,width=320,height=240 ! videoconvert ! 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
#define GST_GP_FORMAT "[gp %8" G_GINT64_FORMAT "]"
66
#define GST_GP_CAST(_gp) ((gint64) _gp)
67

Wim Taymans's avatar
Wim Taymans committed
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
82
/* set to 0.5 seconds by default */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
83
84
#define DEFAULT_MAX_DELAY       G_GINT64_CONSTANT(500000000)
#define DEFAULT_MAX_PAGE_DELAY  G_GINT64_CONSTANT(500000000)
85
#define DEFAULT_MAX_TOLERANCE   G_GINT64_CONSTANT(40000000)
Wim Taymans's avatar
Wim Taymans committed
86
87
enum
{
88
89
  ARG_0,
  ARG_MAX_DELAY,
90
  ARG_MAX_PAGE_DELAY,
91
  ARG_MAX_TOLERANCE
Wim Taymans's avatar
Wim Taymans committed
92
93
94
95
96
97
98
99
100
101
102
};

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

110
static void gst_ogg_mux_finalize (GObject * object);
Wim Taymans's avatar
Wim Taymans committed
111

Wim Taymans's avatar
Wim Taymans committed
112
113
static GstFlowReturn
gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
114
115
static gboolean gst_ogg_mux_handle_src_event (GstPad * pad, GstEvent * event);
static GstPad *gst_ogg_mux_request_new_pad (GstElement * element,
116
    GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
117
118
static void gst_ogg_mux_release_pad (GstElement * element, GstPad * pad);

Wim Taymans's avatar
Wim Taymans committed
119
120
121
122
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);
123
124
static GstStateChangeReturn gst_ogg_mux_change_state (GstElement * element,
    GstStateChange transition);
Wim Taymans's avatar
Wim Taymans committed
125
126

/*static guint gst_ogg_mux_signals[LAST_SIGNAL] = { 0 }; */
127
128
129
#define gst_ogg_mux_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstOggMux, gst_ogg_mux, GST_TYPE_ELEMENT,
    G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL));
Wim Taymans's avatar
Wim Taymans committed
130
131
132
133
134
135
136
137
138
139

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

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

140
  gobject_class->finalize = gst_ogg_mux_finalize;
141
142
143
  gobject_class->get_property = gst_ogg_mux_get_property;
  gobject_class->set_property = gst_ogg_mux_set_property;

144
145
146
147
148
149
150
151
152
153
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&src_factory));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&sink_factory));

  gst_element_class_set_details_simple (gstelement_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
154
  gstelement_class->request_new_pad = gst_ogg_mux_request_new_pad;
155
  gstelement_class->release_pad = gst_ogg_mux_release_pad;
Wim Taymans's avatar
Wim Taymans committed
156

157
158
159
  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,
160
161
          DEFAULT_MAX_DELAY,
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
162
163
164
  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,
165
166
          DEFAULT_MAX_PAGE_DELAY,
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
167
168
169
170
171
  g_object_class_install_property (gobject_class, ARG_MAX_TOLERANCE,
      g_param_spec_uint64 ("max-tolerance", "Max time tolerance",
          "Maximum timestamp difference for maintaining perfect granules",
          0, G_MAXUINT64, DEFAULT_MAX_TOLERANCE,
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
172

Wim Taymans's avatar
Wim Taymans committed
173
174
175
176
  gstelement_class->change_state = gst_ogg_mux_change_state;

}

Wim Taymans's avatar
Wim Taymans committed
177
#if 0
Wim Taymans's avatar
Wim Taymans committed
178
179
180
181
182
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
183
    {GST_EVENT_DISCONTINUOUS, 0},
Wim Taymans's avatar
Wim Taymans committed
184
185
186
187
188
    {0,}
  };

  return gst_ogg_mux_sink_event_masks;
}
Wim Taymans's avatar
Wim Taymans committed
189
#endif
Wim Taymans's avatar
Wim Taymans committed
190

191
192
193
194
195
196
static void
gst_ogg_mux_clear (GstOggMux * ogg_mux)
{
  ogg_mux->pulling = NULL;
  ogg_mux->need_headers = TRUE;
  ogg_mux->delta_pad = NULL;
197
198
  ogg_mux->offset = 0;
  ogg_mux->next_ts = 0;
199
  ogg_mux->last_ts = GST_CLOCK_TIME_NONE;
200
201
}

Wim Taymans's avatar
Wim Taymans committed
202
203
204
205
206
207
208
209
210
211
212
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);

213
  GST_OBJECT_FLAG_SET (GST_ELEMENT (ogg_mux), GST_OGG_FLAG_BOS);
Wim Taymans's avatar
Wim Taymans committed
214
215
216
217

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

218
219
  ogg_mux->collect = gst_collect_pads_new ();
  gst_collect_pads_set_function (ogg_mux->collect,
220
221
      (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_ogg_mux_collected),
      ogg_mux);
222

223
224
  ogg_mux->max_delay = DEFAULT_MAX_DELAY;
  ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;
225
  ogg_mux->max_tolerance = DEFAULT_MAX_TOLERANCE;
226

227
  gst_ogg_mux_clear (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
228
229
}

230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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);
}

245
246
247
static void
gst_ogg_mux_ogg_pad_destroy_notify (GstCollectData * data)
{
248
  GstOggPadData *oggpad = (GstOggPadData *) data;
249
250
  GstBuffer *buf;

251
  ogg_stream_clear (&oggpad->map.stream);
252
  gst_caps_replace (&oggpad->map.caps, NULL);
253
254
255
256
257
258
259
260
261
262

  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
263
static GstPadLinkReturn
264
gst_ogg_mux_sinkconnect (GstPad * pad, GstPad * peer)
Wim Taymans's avatar
Wim Taymans committed
265
266
267
268
269
{
  GstOggMux *ogg_mux;

  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));

270
  GST_DEBUG_OBJECT (ogg_mux, "sinkconnect triggered on %s", GST_PAD_NAME (pad));
271

272
  gst_object_unref (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
273
274
275
276

  return GST_PAD_LINK_OK;
}

277
278
279
280
static gboolean
gst_ogg_mux_sink_event (GstPad * pad, GstEvent * event)
{
  GstOggMux *ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));
281
  GstOggPadData *ogg_pad = (GstOggPadData *) gst_pad_get_element_private (pad);
282
  gboolean ret = FALSE;
283

284
  GST_DEBUG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
285
286

  switch (GST_EVENT_TYPE (event)) {
Wim Taymans's avatar
Wim Taymans committed
287
288
    case GST_EVENT_SEGMENT:
    {
289
      const GstSegment *segment;
290

Wim Taymans's avatar
Wim Taymans committed
291
      gst_event_parse_segment (event, &segment);
292
293

      /* We don't support non time NEWSEGMENT events */
294
      if (segment->format != GST_FORMAT_TIME) {
295
        gst_event_unref (event);
296
297
        event = NULL;
        break;
298
      }
299

300
      gst_segment_copy_into (segment, &ogg_pad->segment);
301
      break;
302
    }
303
    case GST_EVENT_FLUSH_STOP:{
304
      gst_segment_init (&ogg_pad->segment, GST_FORMAT_TIME);
305
      break;
306
    }
307
308
309
310
311
    default:
      break;
  }

  /* now GstCollectPads can take care of the rest, e.g. EOS */
312
  if (event != NULL)
313
314
315
316
317
318
    ret = ogg_pad->collect_event (pad, event);

  gst_object_unref (ogg_mux);
  return ret;
}

319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
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
347
348
static GstPad *
gst_ogg_mux_request_new_pad (GstElement * element,
349
    GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
Wim Taymans's avatar
Wim Taymans committed
350
351
352
{
  GstOggMux *ogg_mux;
  GstPad *newpad;
Wim Taymans's avatar
Wim Taymans committed
353
  GstElementClass *klass;
Wim Taymans's avatar
Wim Taymans committed
354
355
356

  g_return_val_if_fail (templ != NULL, NULL);

Wim Taymans's avatar
Wim Taymans committed
357
358
  if (templ->direction != GST_PAD_SINK)
    goto wrong_direction;
Wim Taymans's avatar
Wim Taymans committed
359
360
361
362

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

Wim Taymans's avatar
Wim Taymans committed
363
364
365
366
367
368
  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
369
370
371
372
373
    gint serial;
    gchar *name;

    if (req_name == NULL || strlen (req_name) < 6) {
      /* no name given when requesting the pad, use random serial number */
374
      serial = gst_ogg_mux_generate_serialno (ogg_mux);
Wim Taymans's avatar
Wim Taymans committed
375
376
377
378
379
    } 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
380
    GST_DEBUG_OBJECT (ogg_mux, "Creating new pad for serial %d", serial);
Wim Taymans's avatar
Wim Taymans committed
381
382
383
384
385
386
387
    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 */
    {
388
      GstOggPadData *oggpad;
Wim Taymans's avatar
Wim Taymans committed
389

390
      oggpad = (GstOggPadData *)
391
          gst_collect_pads_add_pad_full (ogg_mux->collect, newpad,
392
          sizeof (GstOggPadData), gst_ogg_mux_ogg_pad_destroy_notify);
393
      ogg_mux->active_pads++;
Wim Taymans's avatar
Wim Taymans committed
394

395
396
      oggpad->map.serialno = serial;
      ogg_stream_init (&oggpad->map.stream, oggpad->map.serialno);
Wim Taymans's avatar
Wim Taymans committed
397
398
399
400
401
      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;
402
403
404
      oggpad->new_page = TRUE;
      oggpad->first_delta = FALSE;
      oggpad->prev_delta = FALSE;
405
      oggpad->data_pushed = FALSE;
406
      oggpad->pagebuffers = g_queue_new ();
407
408
      oggpad->map.headers = NULL;
      oggpad->map.queued = NULL;
409
410
      oggpad->next_granule = 0;
      oggpad->keyframe_granule = -1;
411

412
      gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
413
414
415
416

      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
417
418
419
420
421
    }
  }

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

Wim Taymans's avatar
Wim Taymans committed
423
424
425
426
  /* dd the pad to the element */
  gst_element_add_pad (element, newpad);

  return newpad;
Wim Taymans's avatar
Wim Taymans committed
427
428
429
430
431
432
433
434
435
436
437
438

  /* 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
439
440
}

441
442
443
444
445
446
447
static void
gst_ogg_mux_release_pad (GstElement * element, GstPad * pad)
{
  GstOggMux *ogg_mux;

  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));

448
  gst_collect_pads_remove_pad (ogg_mux->collect, pad);
449
  gst_element_remove_pad (element, pad);
450
451

  gst_object_unref (ogg_mux);
452
453
}

Wim Taymans's avatar
Wim Taymans committed
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
/* 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);
}

473
static GstBuffer *
474
gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
Wim Taymans's avatar
Wim Taymans committed
475
476
477
478
{
  GstBuffer *buffer;

  /* allocate space for header and body */
Wim Taymans's avatar
Wim Taymans committed
479
  buffer = gst_buffer_new_and_alloc (page->header_len + page->body_len);
480
481
  gst_buffer_fill (buffer, 0, page->header, page->header_len);
  gst_buffer_fill (buffer, page->header_len, page->body, page->body_len);
Wim Taymans's avatar
Wim Taymans committed
482

483
484
485
486
  /* 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);
487
  if (delta)
488
    GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
Wim Taymans's avatar
Wim Taymans committed
489

490
  GST_LOG_OBJECT (mux, GST_GP_FORMAT
491
492
      " created buffer %p from ogg page",
      GST_GP_CAST (ogg_page_granulepos (page)), buffer);
493

494
495
496
  return buffer;
}

Wim Taymans's avatar
Wim Taymans committed
497
static GstFlowReturn
498
499
gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer,
    GstOggPadData * oggpad)
500
{
501
502
  /* fix up OFFSET and OFFSET_END again */
  GST_BUFFER_OFFSET (buffer) = mux->offset;
503
  mux->offset += gst_buffer_get_size (buffer);
504
  GST_BUFFER_OFFSET_END (buffer) = mux->offset;
505

506
507
  /* Ensure we have monotonically increasing timestamps in the output. */
  if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
508
    gint64 run_time = GST_BUFFER_TIMESTAMP (buffer);
509
    if (mux->last_ts != GST_CLOCK_TIME_NONE && run_time < mux->last_ts)
510
511
      GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts;
    else
512
      mux->last_ts = run_time;
513
514
  }

515
516
517
518
519
520
521
522
523
  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;
524
  GstOggPadData *opad = NULL;   /* "oldest" pad */
525
526
527
528
529
530
531
532
  GstClockTime oldest = GST_CLOCK_TIME_NONE;
  GstBuffer *buf = NULL;
  gboolean ret = FALSE;

  *flowret = GST_FLOW_OK;

  walk = mux->collect->data;
  while (walk) {
533
    GstOggPadData *pad = (GstOggPadData *) walk->data;
534
535
536
537
538
539
540

    /* 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) {
541
542
        GST_LOG_OBJECT (pad->collect.pad,
            "pad is EOS, skipping for dequeue decision");
543
      } else {
544
545
        GST_LOG_OBJECT (pad->collect.pad,
            "no pages in this queue, can't dequeue");
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
        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) {
563
564
        GST_LOG_OBJECT (pad->collect.pad,
            "No page timestamps in queue, can't dequeue");
565
566
567
568
569
570
571
572
573
        return FALSE;
      }
    }

    walk = g_slist_next (walk);
  }

  walk = mux->collect->data;
  while (walk) {
574
    GstOggPadData *pad = (GstOggPadData *) walk->data;
575

576
    /* any page with a granulepos of -1 can be pushed immediately.
577
578
579
     * 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
580
      GST_LOG_OBJECT (pad->collect.pad, "[gp        -1] pushing page");
581
      g_queue_pop_head (pad->pagebuffers);
582
      *flowret = gst_ogg_mux_push_buffer (mux, buf, pad);
583
584
585
586
587
588
589
      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) {
590
591
592
593
        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);
594
595
596
        opad = pad;
      } else {
        /* if we have an oldest, compare with this one */
597
598
599
600
601
        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);
602
603
604
605
606
607
608
609
610
611
          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
612
    GST_LOG_OBJECT (opad->collect.pad,
613
614
615
        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)));
616
    *flowret = gst_ogg_mux_push_buffer (mux, buf, opad);
617
618
619
620
621
622
    ret = TRUE;
  }

  return ret;
}

623
624
625
626
627
628
629
630
631
632
633
634
635
/* 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.
 */
636
static GstFlowReturn
637
638
gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPadData * pad,
    ogg_page * page, gboolean delta)
639
{
Wim Taymans's avatar
Wim Taymans committed
640
  GstFlowReturn ret;
641
  GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
642

643
  /* take the timestamp of the first packet on this page */
644
  GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp;
645
  GST_BUFFER_DURATION (buffer) = pad->timestamp_end - pad->timestamp;
646
647
  /* take the gp time of the last completed packet on this page */
  GST_BUFFER_OFFSET (buffer) = pad->gp_time;
648

649
  /* the next page will start where the current page's end time leaves off */
650
651
  pad->timestamp = pad->timestamp_end;

652
  g_queue_push_tail (pad->pagebuffers, buffer);
653
654
655
  GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
      " queued buffer page %p (gp time %"
      GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT
656
      "), %d page buffers queued", GST_GP_CAST (ogg_page_granulepos (page)),
657
      buffer, GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)),
658
659
      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
      g_queue_get_length (pad->pagebuffers));
Wim Taymans's avatar
Wim Taymans committed
660

661
662
663
664
  while (gst_ogg_mux_dequeue_page (mux, &ret)) {
    if (ret != GST_FLOW_OK)
      break;
  }
Wim Taymans's avatar
Wim Taymans committed
665
666

  return ret;
Wim Taymans's avatar
Wim Taymans committed
667
668
669
}

/*
670
671
672
673
674
675
676
 * 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
677
678
 */
static gint
679
680
gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPadData * first,
    GstOggPadData * second)
Wim Taymans's avatar
Wim Taymans committed
681
{
682
  guint64 firsttime, secondtime;
Wim Taymans's avatar
Wim Taymans committed
683

684
685
  /* if the first pad doesn't contain anything or is even NULL, return
   * the second pad as best candidate and vice versa */
686
  if (first == NULL)
Wim Taymans's avatar
Wim Taymans committed
687
    return 1;
688
  if (second == NULL)
Wim Taymans's avatar
Wim Taymans committed
689
690
    return -1;

691
  /* no timestamp on first buffer, it must go first */
692
  firsttime = GST_BUFFER_TIMESTAMP (first->buffer);
693
  if (firsttime == GST_CLOCK_TIME_NONE)
Wim Taymans's avatar
Wim Taymans committed
694
695
    return -1;

696
  /* no timestamp on second buffer, it must go first */
697
  secondtime = GST_BUFFER_TIMESTAMP (second->buffer);
698
  if (secondtime == GST_CLOCK_TIME_NONE)
Wim Taymans's avatar
Wim Taymans committed
699
700
    return 1;

701
702
  /* first buffer has higher timestamp, second one should go first */
  if (secondtime < firsttime)
Wim Taymans's avatar
Wim Taymans committed
703
    return 1;
704
705
  /* second buffer has higher timestamp, first one should go first */
  else if (secondtime > firsttime)
Wim Taymans's avatar
Wim Taymans committed
706
707
708
709
    return -1;
  else {
    /* buffers with equal timestamps, prefer the pad that has the
     * least number of pages muxed */
710
    if (second->pageno < first->pageno)
Wim Taymans's avatar
Wim Taymans committed
711
      return 1;
712
    else if (second->pageno > first->pageno)
Wim Taymans's avatar
Wim Taymans committed
713
714
715
716
717
718
719
      return -1;
  }

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

720
721
722
723
724
static GstBuffer *
gst_ogg_mux_decorate_buffer (GstOggMux * ogg_mux, GstOggPadData * pad,
    GstBuffer * buf)
{
  GstClockTime time;
725
726
727
728
  gint64 duration, granule, limit;
  GstClockTime next_time;
  GstClockTimeDiff diff;
  ogg_packet packet;
Wim Taymans's avatar
Wim Taymans committed
729
  gsize size;
730
731

  /* ensure messing with metadata is ok */
Wim Taymans's avatar
Wim Taymans committed
732
  buf = gst_buffer_make_writable (buf);
733
734
735
736
737
738
739
740
741
742
743
744
745

  /* convert time to running time, so we need no longer bother about that */
  time = GST_BUFFER_TIMESTAMP (buf);
  if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (time))) {
    time = gst_segment_to_running_time (&pad->segment, GST_FORMAT_TIME, time);
    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) {
      gst_buffer_unref (buf);
      return NULL;
    } else {
      GST_BUFFER_TIMESTAMP (buf) = time;
    }
  }

746
747
748
749
750
  /* now come up with granulepos stuff corresponding to time */
  if (!pad->have_type ||
      pad->map.granulerate_n <= 0 || pad->map.granulerate_d <= 0)
    goto no_granule;

Wim Taymans's avatar
Wim Taymans committed
751
752
  packet.packet = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
  packet.bytes = size;
753
  duration = gst_ogg_stream_get_packet_duration (&pad->map, &packet);
Wim Taymans's avatar
Wim Taymans committed
754
  gst_buffer_unmap (buf, packet.packet, size);
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817

  /* give up if no duration can be determined, relying on upstream */
  if (G_UNLIKELY (duration < 0)) {
    /* well, if some day we really could handle sparse input ... */
    if (pad->map.is_sparse) {
      limit = 1;
      diff = 2;
      goto resync;
    }
    GST_WARNING_OBJECT (pad->collect.pad,
        "failed to determine packet duration");
    goto no_granule;
  }

  GST_LOG_OBJECT (pad->collect.pad, "buffer ts %" GST_TIME_FORMAT
      ", duration %" GST_TIME_FORMAT ", granule duration %" G_GINT64_FORMAT,
      GST_TIME_ARGS (time), GST_TIME_ARGS (GST_BUFFER_DURATION (buf)),
      duration);

  /* determine granule corresponding to time,
   * using the inverse of oggdemux' granule -> time */

  /* see if interpolated granule matches good enough */
  granule = pad->next_granule;
  next_time = gst_ogg_stream_granule_to_time (&pad->map, pad->next_granule);
  diff = GST_CLOCK_DIFF (next_time, time);

  /* we tolerate deviation up to configured or within granule granularity */
  limit = gst_ogg_stream_granule_to_time (&pad->map, 1) / 2;
  limit = MAX (limit, ogg_mux->max_tolerance);

  GST_LOG_OBJECT (pad->collect.pad, "expected granule %" G_GINT64_FORMAT " == "
      "time %" GST_TIME_FORMAT " --> ts diff %" GST_TIME_FORMAT
      " < tolerance %" GST_TIME_FORMAT " (?)",
      granule, GST_TIME_ARGS (next_time), GST_TIME_ARGS (ABS (diff)),
      GST_TIME_ARGS (limit));

resync:
  /* if not good enough, determine granule based on time */
  if (diff > limit || diff < -limit) {
    granule = gst_util_uint64_scale_round (time, pad->map.granulerate_n,
        GST_SECOND * pad->map.granulerate_d);
    GST_DEBUG_OBJECT (pad->collect.pad,
        "resyncing to determined granule %" G_GINT64_FORMAT, granule);
  }

  if (pad->map.is_ogm || pad->map.is_sparse) {
    pad->next_granule = granule;
  } else {
    granule += duration;
    pad->next_granule = granule;
  }

  /* track previous keyframe */
  if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
    pad->keyframe_granule = granule;

  /* determine corresponding time and granulepos */
  GST_BUFFER_OFFSET (buf) = gst_ogg_stream_granule_to_time (&pad->map, granule);
  GST_BUFFER_OFFSET_END (buf) =
      gst_ogg_stream_granule_to_granulepos (&pad->map, granule,
      pad->keyframe_granule);

818
  return buf;
819
820
821
822
823
824
825
826

  /* ERRORS */
no_granule:
  {
    GST_DEBUG_OBJECT (pad->collect.pad, "could not determine granulepos, "
        "falling back to upstream provided metadata");
    return buf;
  }
827
828
829
}


830
831
832
833
834
835
836
837
838
839
840
/* 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
841
842
 * with the lowest timestamp. If best->buffer == NULL then either
 * we're at EOS (popped = FALSE), or a buffer got dropped, so retry. */
843
static GstOggPadData *
844
gst_ogg_mux_queue_pads (GstOggMux * ogg_mux, gboolean * popped)
Wim Taymans's avatar
Wim Taymans committed
845
{
846
  GstOggPadData *bestpad = NULL;
Wim Taymans's avatar
Wim Taymans committed
847
848
  GSList *walk;

849
850
  *popped = FALSE;

Wim Taymans's avatar
Wim Taymans committed
851
  /* try to make sure we have a buffer from each usable pad first */
Wim Taymans's avatar
Wim Taymans committed
852
  walk = ogg_mux->collect->data;
Wim Taymans's avatar
Wim Taymans committed
853
  while (walk) {
854
    GstOggPadData *pad;
Wim Taymans's avatar
Wim Taymans committed
855
    GstCollectData *data;
Wim Taymans's avatar
Wim Taymans committed
856

Wim Taymans's avatar
Wim Taymans committed
857
    data = (GstCollectData *) walk->data;
858
    pad = (GstOggPadData *) data;
Wim Taymans's avatar
Wim Taymans committed
859

Wim Taymans's avatar
Wim Taymans committed
860
861
    walk = g_slist_next (walk);

862
    GST_LOG_OBJECT (data->pad, "looking at pad for buffer");
863

Wim Taymans's avatar
Wim Taymans committed
864
    /* try to get a new buffer for this pad if needed and possible */
Wim Taymans's avatar
Wim Taymans committed
865
866
    if (pad->buffer == NULL) {
      GstBuffer *buf;
Wim Taymans's avatar
Wim Taymans committed
867

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

871
872
      /* On EOS we get a NULL buffer */
      if (buf != NULL) {
873
874
        *popped = TRUE;

875
876
877
878
        if (ogg_mux->delta_pad == NULL &&
            GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
          ogg_mux->delta_pad = pad;

879
880
881
        /* if we need headers */
        if (pad->state == GST_OGG_PAD_STATE_CONTROL) {
          /* and we have one */
882
          ogg_packet packet;
883
          gboolean is_header;
884
          gsize size;
885

886
887
          packet.packet = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
          packet.bytes = size;
888

889
890
          /* if we're not yet in data mode, ensure we're setup on the first packet */
          if (!pad->have_type) {
Wim Taymans's avatar
Wim Taymans committed
891
892
            GstCaps *caps;

893
894
895
896
            /* Use headers in caps, if any; this will allow us to be resilient
             * to starting streams on the fly, and some streams (like VP8
             * at least) do not send headers packets, as other muxers don't
             * expect/need them. */
897
            caps = gst_pad_get_current_caps (GST_PAD_CAST (data->pad));
898
            pad->have_type =
Wim Taymans's avatar
Wim Taymans committed
899
                gst_ogg_stream_setup_map_from_caps_headers (&pad->map, caps);
900
901
902
903
904

            if (!pad->have_type) {
              /* fallback on the packet */
              pad->have_type = gst_ogg_stream_setup_map (&pad->map, &packet);
            }
905
            if (!pad->have_type) {
906
              GST_ERROR_OBJECT (pad, "mapper didn't recognise input stream "
Wim Taymans's avatar
Wim Taymans committed
907
                  "(pad caps: %" GST_PTR_FORMAT ")", caps);
908
909
910
            } else {
              GST_DEBUG_OBJECT (pad, "caps detected: %" GST_PTR_FORMAT,
                  pad->map.caps);
911
            }
Wim Taymans's avatar
Wim Taymans committed
912
913
            if (caps)
              gst_caps_unref (caps);
914
915
          }

916
917
918
919
          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);
920

921
922
          gst_buffer_unmap (buf, packet.packet, size);

923
          if (is_header) {
924
            GST_DEBUG_OBJECT (ogg_mux,
925
                "got header buffer in control state, ignoring");
926
            /* just ignore */
927
            pad->map.n_header_packets_seen++;
928
929
930
            gst_buffer_unref (buf);
            buf = NULL;
          } else {
931
            GST_DEBUG_OBJECT (ogg_mux,
Vincent Penquerc'h's avatar
Vincent Penquerc'h committed
932
                "got data buffer in control state, switching to data mode");
933
934
            /* this is a data buffer so switch to data state */
            pad->state = GST_OGG_PAD_STATE_DATA;
935
936
937
938
939
940
941
942
943

            /* check if this type of stream allows generating granulepos
             * metadata here, if not, upstream will have to provide */
            if (gst_ogg_stream_granule_to_granulepos (&pad->map, 1, 1) < 0) {
              GST_WARNING_OBJECT (data->pad, "can not generate metadata; "
                  "relying on upstream");
              /* disable metadata code path, otherwise not used anyway */
              pad->map.granulerate_n = 0;
            }
944
          }
Wim Taymans's avatar
Wim Taymans committed
945
        }
946
947
948
949
950
951
952
953

        /* so now we should have a real data packet;
         * see that it is properly decorated */
        if (G_LIKELY (buf)) {
          buf = gst_ogg_mux_decorate_buffer (ogg_mux, pad, buf);
          if (G_UNLIKELY (!buf))
            GST_DEBUG_OBJECT (data->pad, "buffer clipped");
        }
Wim Taymans's avatar
Wim Taymans committed
954
      }
955

956
      pad->buffer = buf;
Wim Taymans's avatar
Wim Taymans committed
957
    }
Wim Taymans's avatar
Wim Taymans committed
958
959
960

    /* we should have a buffer now, see if it is the best pad to
     * pull on */
961
    if (pad->buffer) {
962
      if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) {
963
        GST_LOG_OBJECT (data->pad,
964
            "new best pad, with buffer %" GST_PTR_FORMAT, pad->buffer);
965

Wim Taymans's avatar
Wim Taymans committed
966
        bestpad = pad;
967
      }
Wim Taymans's avatar
Wim Taymans committed
968
969
    }
  }
970

971
  return bestpad;
Wim Taymans's avatar
Wim Taymans committed
972
973
}

974
static GList *
975
gst_ogg_mux_get_headers (GstOggPadData * pad)
976
977
978
{
  GList *res = NULL;
  GstStructure *structure;
979
  GstCaps *caps;
Wim Taymans's avatar
Wim Taymans committed
980
  GstPad *thepad;
981

Wim Taymans's avatar
Wim Taymans committed
982
  thepad = pad->collect.pad;
983

984
  GST_LOG_OBJECT (thepad, "getting headers");
Wim Taymans's avatar
Wim Taymans committed
985
986

  caps = gst_pad_get_negotiated_caps (thepad);
987
988
989
990
  if (caps != NULL) {
    const GValue *streamheader;

    structure = gst_caps_get_structure (caps, 0);
991
992
993
994
995
996
    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;
997

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

1000
1001
        for (i = 0; i < bufarr->len; i++) {
          GValue *bufval = &g_array_index (bufarr, GValue, i);
1002

1003
1004
1005
          GST_LOG_OBJECT (thepad, "item %d", i);
          if (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER) {
            GstBuffer *buf = g_value_peek_pointer (bufval);
1006

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

1009
1010
            gst_buffer_ref (buf);
            res = g_list_append (res, buf);
1011
1012
          }
        }
Wim Taymans's avatar
Wim Taymans committed
1013
      } else {
1014
        GST_LOG_OBJECT (thepad, "streamheader is not fixed list");
1015
      }
1016
1017
1018
1019

      /* Start a new page for every CMML buffer */
      if (gst_structure_has_name (structure, "text/x-cmml"))
        pad->always_flush_page = TRUE;
1020
1021
    } else if (gst_structure_has_name (structure, "video/x-dirac")) {
      res = g_list_append (res, pad->buffer);
1022
      pad->buffer = NULL;
1023
      pad->always_flush_page = TRUE;
1024
1025
    } else {
      GST_LOG_OBJECT (thepad, "caps don't have streamheader");
1026
    }
1027
    gst_caps_unref (caps);
Wim Taymans's avatar
Wim Taymans committed
1028
  } else {
1029
    GST_LOG_OBJECT (thepad, "got empty caps as negotiated format");
1030
1031
1032
1033
  }
  return res;
}

Wim Taymans's avatar
Wim Taymans committed
1034
static GstCaps *
1035
1036
gst_ogg_mux_set_header_on_caps (GstCaps * caps, GList * buffers)
{
Wim Taymans's avatar
Wim Taymans committed
1037
  GstStructure *structure;
1038
  GValue array = { 0 };
1039
1040
  GList *walk = buffers;

Wim Taymans's avatar
Wim Taymans committed
1041
1042
1043
1044
  caps = gst_caps_make_writable (caps);

  structure = gst_caps_get_structure (caps, 0);

1045
  /* put buffers in a fixed list */
1046
  g_value_init (&array, GST_TYPE_ARRAY);
1047
1048
1049

  while (walk) {
    GstBuffer *buf = GST_BUFFER (walk->data);
1050
    GstBuffer *copy;
1051
1052
1053
1054
1055
    GValue value = { 0 };

    walk = walk->next;

    /* mark buffer */
1056
1057
    GST_LOG ("Setting IN_CAPS on buffer of length %d",
        gst_buffer_get_size (buf));
1058
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
1059
1060

    g_value_init (&value, GST_TYPE_BUFFER);
1061
1062
1063
    copy = gst_buffer_copy (buf);
    gst_value_set_buffer (&value, copy);
    gst_buffer_unref (copy);
1064
    gst_value_array_append_value (&array, &value);
1065
1066
    g_value_unset (&value);
  }
1067
1068
  gst_structure_set_value (structure, "streamheader", &array);
  g_value_unset (&array);
Wim Taymans's avatar
Wim Taymans committed
1069
1070

  return caps;
1071
1072
}

1073
/*
1074
1075
1076
1077
1078
1079
1080
 * 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
1081
static GstFlowReturn
1082
1083
1084
1085
1086
gst_ogg_mux_send_headers (GstOggMux * mux)
{
  GSList *walk;
  GList *hbufs, *hwalk;
  GstCaps *caps;
Wim Taymans's avatar
Wim Taymans committed
1087
  GstFlowReturn ret;
1088
1089

  hbufs = NULL;
Wim Taymans's avatar
Wim Taymans committed
1090
  ret = GST_FLOW_OK;
1091