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