Blame ext/ogg/gstoggmux.c

Packit 971217
/* OGG muxer plugin for GStreamer
Packit 971217
 * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
Packit 971217
 * Copyright (C) 2006 Thomas Vander Stichele <thomas at apestaart dot org>
Packit 971217
 *
Packit 971217
 * This library is free software; you can redistribute it and/or
Packit 971217
 * modify it under the terms of the GNU Library General Public
Packit 971217
 * License as published by the Free Software Foundation; either
Packit 971217
 * version 2 of the License, or (at your option) any later version.
Packit 971217
 *
Packit 971217
 * This library is distributed in the hope that it will be useful,
Packit 971217
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 971217
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 971217
 * Library General Public License for more details.
Packit 971217
 *
Packit 971217
 * You should have received a copy of the GNU Library General Public
Packit 971217
 * License along with this library; if not, write to the
Packit 971217
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
Packit 971217
 * Boston, MA 02110-1301, USA.
Packit 971217
 */
Packit 971217
Packit 971217
/**
Packit 971217
 * SECTION:element-oggmux
Packit 971217
 * @title: oggmux
Packit 971217
 * @see_also: <link linkend="gst-plugins-base-plugins-oggdemux">oggdemux</link>
Packit 971217
 *
Packit 971217
 * This element merges streams (audio and video) into ogg files.
Packit 971217
 *
Packit 971217
 * ## Example pipelines
Packit 971217
 * |[
Packit 971217
 * gst-launch-1.0 v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! videorate ! theoraenc ! oggmux ! filesink location=video.ogg
Packit 971217
 * ]|
Packit 971217
 * Encodes a video stream captured from a v4l2-compatible camera to Ogg/Theora
Packit 971217
 * (the encoding will stop automatically after 500 frames)
Packit 971217
 *
Packit 971217
 */
Packit 971217
Packit 971217
#ifdef HAVE_CONFIG_H
Packit 971217
#include "config.h"
Packit 971217
#endif
Packit 971217
Packit 971217
#include <gst/gst.h>
Packit 971217
#include <gst/base/gstbytewriter.h>
Packit 971217
#include <gst/audio/audio.h>
Packit 971217
#include <gst/tag/tag.h>
Packit 971217
Packit 971217
#include "gstoggmux.h"
Packit 971217
Packit 971217
/* memcpy - if someone knows a way to get rid of it, please speak up
Packit 971217
 * note: the ogg docs even say you need this... */
Packit 971217
#include <string.h>
Packit 971217
#include <time.h>
Packit 971217
#include <stdlib.h>             /* rand, srand, atoi */
Packit 971217
Packit 971217
GST_DEBUG_CATEGORY_STATIC (gst_ogg_mux_debug);
Packit 971217
#define GST_CAT_DEFAULT gst_ogg_mux_debug
Packit 971217
Packit 971217
/* This isn't generally what you'd want with an end-time macro, because
Packit 971217
   technically the end time of a buffer with invalid duration is invalid. But
Packit 971217
   for sorting ogg pages this is what we want. */
Packit 971217
#define GST_BUFFER_END_TIME(buf) \
Packit 971217
    (GST_BUFFER_DURATION_IS_VALID (buf) \
Packit 971217
    ? GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf) \
Packit 971217
    : GST_BUFFER_TIMESTAMP (buf))
Packit 971217
Packit 971217
#define GST_GP_FORMAT "[gp %8" G_GINT64_FORMAT "]"
Packit 971217
#define GST_GP_CAST(_gp) ((gint64) _gp)
Packit 971217
Packit 971217
/* set to 0.5 seconds by default */
Packit 971217
#define DEFAULT_MAX_DELAY       G_GINT64_CONSTANT(500000000)
Packit 971217
#define DEFAULT_MAX_PAGE_DELAY  G_GINT64_CONSTANT(500000000)
Packit 971217
#define DEFAULT_MAX_TOLERANCE   G_GINT64_CONSTANT(40000000)
Packit 971217
#define DEFAULT_SKELETON        FALSE
Packit 971217
Packit 971217
enum
Packit 971217
{
Packit 971217
  ARG_0,
Packit 971217
  ARG_MAX_DELAY,
Packit 971217
  ARG_MAX_PAGE_DELAY,
Packit 971217
  ARG_MAX_TOLERANCE,
Packit 971217
  ARG_SKELETON
Packit 971217
};
Packit 971217
Packit 971217
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
Packit 971217
    GST_PAD_SRC,
Packit 971217
    GST_PAD_ALWAYS,
Packit 971217
    GST_STATIC_CAPS ("application/ogg; audio/ogg; video/ogg")
Packit 971217
    );
Packit 971217
Packit 971217
static GstStaticPadTemplate video_sink_factory =
Packit 971217
    GST_STATIC_PAD_TEMPLATE ("video_%u",
Packit 971217
    GST_PAD_SINK,
Packit 971217
    GST_PAD_REQUEST,
Packit 971217
    GST_STATIC_CAPS ("video/x-theora; "
Packit 971217
        "application/x-ogm-video; video/x-dirac; "
Packit 971217
        "video/x-smoke; video/x-vp8; video/x-daala")
Packit 971217
    );
Packit 971217
Packit 971217
static GstStaticPadTemplate audio_sink_factory =
Packit 971217
    GST_STATIC_PAD_TEMPLATE ("audio_%u",
Packit 971217
    GST_PAD_SINK,
Packit 971217
    GST_PAD_REQUEST,
Packit 971217
    GST_STATIC_CAPS
Packit 971217
    ("audio/x-vorbis; audio/x-flac; audio/x-speex; audio/x-celt; "
Packit 971217
        "application/x-ogm-audio; audio/x-opus")
Packit 971217
    );
Packit 971217
Packit 971217
static GstStaticPadTemplate subtitle_sink_factory =
Packit 971217
    GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
Packit 971217
    GST_PAD_SINK,
Packit 971217
    GST_PAD_REQUEST,
Packit 971217
    GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) TRUE; "
Packit 971217
        "subtitle/x-kate; application/x-kate")
Packit 971217
    );
Packit 971217
Packit 971217
static void gst_ogg_mux_finalize (GObject * object);
Packit 971217
Packit 971217
static GstFlowReturn gst_ogg_mux_collected (GstCollectPads * pads,
Packit 971217
    GstOggMux * ogg_mux);
Packit 971217
static gboolean gst_ogg_mux_sink_event (GstCollectPads * pads,
Packit 971217
    GstCollectData * pad, GstEvent * event, gpointer user_data);
Packit 971217
static gboolean gst_ogg_mux_handle_src_event (GstPad * pad, GstObject * parent,
Packit 971217
    GstEvent * event);
Packit 971217
static GstPad *gst_ogg_mux_request_new_pad (GstElement * element,
Packit 971217
    GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
Packit 971217
static void gst_ogg_mux_release_pad (GstElement * element, GstPad * pad);
Packit 971217
static void gst_ogg_pad_data_reset (GstOggMux * ogg_mux,
Packit 971217
    GstOggPadData * pad_data);
Packit 971217
Packit 971217
static void gst_ogg_mux_set_property (GObject * object,
Packit 971217
    guint prop_id, const GValue * value, GParamSpec * pspec);
Packit 971217
static void gst_ogg_mux_get_property (GObject * object,
Packit 971217
    guint prop_id, GValue * value, GParamSpec * pspec);
Packit 971217
static GstStateChangeReturn gst_ogg_mux_change_state (GstElement * element,
Packit 971217
    GstStateChange transition);
Packit 971217
Packit 971217
/*static guint gst_ogg_mux_signals[LAST_SIGNAL] = { 0 }; */
Packit 971217
#define gst_ogg_mux_parent_class parent_class
Packit 971217
G_DEFINE_TYPE_WITH_CODE (GstOggMux, gst_ogg_mux, GST_TYPE_ELEMENT,
Packit 971217
    G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL));
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_class_init (GstOggMuxClass * klass)
Packit 971217
{
Packit 971217
  GObjectClass *gobject_class;
Packit 971217
  GstElementClass *gstelement_class;
Packit 971217
Packit 971217
  gobject_class = (GObjectClass *) klass;
Packit 971217
  gstelement_class = (GstElementClass *) klass;
Packit 971217
Packit 971217
  gobject_class->finalize = gst_ogg_mux_finalize;
Packit 971217
  gobject_class->get_property = gst_ogg_mux_get_property;
Packit 971217
  gobject_class->set_property = gst_ogg_mux_set_property;
Packit 971217
Packit 971217
  gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
Packit 971217
  gst_element_class_add_static_pad_template (gstelement_class,
Packit 971217
      &video_sink_factory);
Packit 971217
  gst_element_class_add_static_pad_template (gstelement_class,
Packit 971217
      &audio_sink_factory);
Packit 971217
  gst_element_class_add_static_pad_template (gstelement_class,
Packit 971217
      &subtitle_sink_factory);
Packit 971217
Packit 971217
  gst_element_class_set_static_metadata (gstelement_class,
Packit 971217
      "Ogg muxer", "Codec/Muxer",
Packit 971217
      "mux ogg streams (info about ogg: http://xiph.org)",
Packit 971217
      "Wim Taymans <wim@fluendo.com>");
Packit 971217
Packit 971217
  gstelement_class->request_new_pad = gst_ogg_mux_request_new_pad;
Packit 971217
  gstelement_class->release_pad = gst_ogg_mux_release_pad;
Packit 971217
Packit 971217
  g_object_class_install_property (gobject_class, ARG_MAX_DELAY,
Packit 971217
      g_param_spec_uint64 ("max-delay", "Max delay",
Packit 971217
          "Maximum delay in multiplexing streams", 0, G_MAXUINT64,
Packit 971217
          DEFAULT_MAX_DELAY,
Packit 971217
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 971217
  g_object_class_install_property (gobject_class, ARG_MAX_PAGE_DELAY,
Packit 971217
      g_param_spec_uint64 ("max-page-delay", "Max page delay",
Packit 971217
          "Maximum delay for sending out a page", 0, G_MAXUINT64,
Packit 971217
          DEFAULT_MAX_PAGE_DELAY,
Packit 971217
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 971217
  g_object_class_install_property (gobject_class, ARG_MAX_TOLERANCE,
Packit 971217
      g_param_spec_uint64 ("max-tolerance", "Max time tolerance",
Packit 971217
          "Maximum timestamp difference for maintaining perfect granules",
Packit 971217
          0, G_MAXUINT64, DEFAULT_MAX_TOLERANCE,
Packit 971217
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 971217
  g_object_class_install_property (gobject_class, ARG_SKELETON,
Packit 971217
      g_param_spec_boolean ("skeleton", "Skeleton",
Packit 971217
          "Whether to include a Skeleton track",
Packit 971217
          DEFAULT_SKELETON,
Packit 971217
          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 971217
Packit 971217
  gstelement_class->change_state = gst_ogg_mux_change_state;
Packit 971217
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_clear (GstOggMux * ogg_mux)
Packit 971217
{
Packit 971217
  ogg_mux->pulling = NULL;
Packit 971217
  ogg_mux->need_headers = TRUE;
Packit 971217
  ogg_mux->need_start_events = TRUE;
Packit 971217
  ogg_mux->delta_pad = NULL;
Packit 971217
  ogg_mux->offset = 0;
Packit 971217
  ogg_mux->next_ts = 0;
Packit 971217
  ogg_mux->last_ts = GST_CLOCK_TIME_NONE;
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_init (GstOggMux * ogg_mux)
Packit 971217
{
Packit 971217
  GstElementClass *klass = GST_ELEMENT_GET_CLASS (ogg_mux);
Packit 971217
Packit 971217
  ogg_mux->srcpad =
Packit 971217
      gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
Packit 971217
          "src"), "src");
Packit 971217
  gst_pad_set_event_function (ogg_mux->srcpad, gst_ogg_mux_handle_src_event);
Packit 971217
  gst_element_add_pad (GST_ELEMENT (ogg_mux), ogg_mux->srcpad);
Packit 971217
Packit 971217
  /* seed random number generator for creation of serial numbers */
Packit 971217
  srand (time (NULL));
Packit 971217
Packit 971217
  ogg_mux->collect = gst_collect_pads_new ();
Packit 971217
  gst_collect_pads_set_function (ogg_mux->collect,
Packit 971217
      (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_ogg_mux_collected),
Packit 971217
      ogg_mux);
Packit 971217
  gst_collect_pads_set_event_function (ogg_mux->collect,
Packit 971217
      (GstCollectPadsEventFunction) GST_DEBUG_FUNCPTR (gst_ogg_mux_sink_event),
Packit 971217
      ogg_mux);
Packit 971217
Packit 971217
  ogg_mux->max_delay = DEFAULT_MAX_DELAY;
Packit 971217
  ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;
Packit 971217
  ogg_mux->max_tolerance = DEFAULT_MAX_TOLERANCE;
Packit 971217
Packit 971217
  gst_ogg_mux_clear (ogg_mux);
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_finalize (GObject * object)
Packit 971217
{
Packit 971217
  GstOggMux *ogg_mux;
Packit 971217
Packit 971217
  ogg_mux = GST_OGG_MUX (object);
Packit 971217
Packit 971217
  if (ogg_mux->collect) {
Packit 971217
    gst_object_unref (ogg_mux->collect);
Packit 971217
    ogg_mux->collect = NULL;
Packit 971217
  }
Packit 971217
Packit 971217
  G_OBJECT_CLASS (parent_class)->finalize (object);
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_ogg_pad_destroy_notify (GstCollectData * data)
Packit 971217
{
Packit 971217
  GstOggPadData *oggpad = (GstOggPadData *) data;
Packit 971217
  GstBuffer *buf;
Packit 971217
Packit 971217
  ogg_stream_clear (&oggpad->map.stream);
Packit 971217
  gst_caps_replace (&oggpad->map.caps, NULL);
Packit 971217
Packit 971217
  if (oggpad->pagebuffers) {
Packit 971217
    while ((buf = g_queue_pop_head (oggpad->pagebuffers)) != NULL) {
Packit 971217
      gst_buffer_unref (buf);
Packit 971217
    }
Packit 971217
    g_queue_free (oggpad->pagebuffers);
Packit 971217
    oggpad->pagebuffers = NULL;
Packit 971217
  }
Packit 971217
}
Packit 971217
Packit 971217
static GstPadLinkReturn
Packit 971217
gst_ogg_mux_sinkconnect (GstPad * pad, GstObject * parent, GstPad * peer)
Packit 971217
{
Packit 971217
  GstOggMux *ogg_mux;
Packit 971217
Packit 971217
  ogg_mux = GST_OGG_MUX (parent);
Packit 971217
Packit 971217
  GST_DEBUG_OBJECT (ogg_mux, "sinkconnect triggered on %s", GST_PAD_NAME (pad));
Packit 971217
Packit 971217
  return GST_PAD_LINK_OK;
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_flush (GstOggMux * ogg_mux)
Packit 971217
{
Packit 971217
  GSList *walk;
Packit 971217
Packit 971217
  for (walk = ogg_mux->collect->data; walk; walk = g_slist_next (walk)) {
Packit 971217
    GstOggPadData *pad;
Packit 971217
Packit 971217
    pad = (GstOggPadData *) walk->data;
Packit 971217
Packit 971217
    gst_ogg_pad_data_reset (ogg_mux, pad);
Packit 971217
  }
Packit 971217
Packit 971217
  gst_ogg_mux_clear (ogg_mux);
Packit 971217
}
Packit 971217
Packit 971217
static gboolean
Packit 971217
gst_ogg_mux_sink_event (GstCollectPads * pads, GstCollectData * pad,
Packit 971217
    GstEvent * event, gpointer user_data)
Packit 971217
{
Packit 971217
  GstOggMux *ogg_mux = GST_OGG_MUX (user_data);
Packit 971217
  GstOggPadData *ogg_pad = (GstOggPadData *) pad;
Packit 971217
Packit 971217
  GST_DEBUG_OBJECT (pad->pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
Packit 971217
Packit 971217
  switch (GST_EVENT_TYPE (event)) {
Packit 971217
    case GST_EVENT_SEGMENT:
Packit 971217
    {
Packit 971217
      const GstSegment *segment;
Packit 971217
Packit 971217
      gst_event_parse_segment (event, &segment);
Packit 971217
Packit 971217
      /* We don't support non time NEWSEGMENT events */
Packit 971217
      if (segment->format != GST_FORMAT_TIME) {
Packit 971217
        gst_event_unref (event);
Packit 971217
        event = NULL;
Packit 971217
        break;
Packit 971217
      }
Packit 971217
Packit 971217
      gst_segment_copy_into (segment, &ogg_pad->segment);
Packit 971217
      break;
Packit 971217
    }
Packit 971217
    case GST_EVENT_FLUSH_STOP:{
Packit 971217
      /* only a single flush-stop is forwarded from collect pads */
Packit 971217
      gst_ogg_mux_flush (ogg_mux);
Packit 971217
      break;
Packit 971217
    }
Packit 971217
    case GST_EVENT_TAG:{
Packit 971217
      GstTagList *tags;
Packit 971217
Packit 971217
      gst_event_parse_tag (event, &tags);
Packit 971217
      tags = gst_tag_list_merge (ogg_pad->tags, tags, GST_TAG_MERGE_APPEND);
Packit 971217
      if (ogg_pad->tags)
Packit 971217
        gst_tag_list_unref (ogg_pad->tags);
Packit 971217
      ogg_pad->tags = tags;
Packit 971217
Packit 971217
      GST_DEBUG_OBJECT (ogg_mux, "Got tags %" GST_PTR_FORMAT, ogg_pad->tags);
Packit 971217
      break;
Packit 971217
    }
Packit 971217
    default:
Packit 971217
      break;
Packit 971217
  }
Packit 971217
Packit 971217
  /* now GstCollectPads can take care of the rest, e.g. EOS */
Packit 971217
  if (event != NULL)
Packit 971217
    return gst_collect_pads_event_default (pads, pad, event, FALSE);
Packit 971217
Packit 971217
  return TRUE;
Packit 971217
}
Packit 971217
Packit 971217
static gboolean
Packit 971217
gst_ogg_mux_is_serialno_present (GstOggMux * ogg_mux, guint32 serialno)
Packit 971217
{
Packit 971217
  GSList *walk;
Packit 971217
Packit 971217
  walk = ogg_mux->collect->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *pad = (GstOggPadData *) walk->data;
Packit 971217
    if (pad->map.serialno == serialno)
Packit 971217
      return TRUE;
Packit 971217
    walk = walk->next;
Packit 971217
  }
Packit 971217
Packit 971217
  return FALSE;
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_pad_data_reset (GstOggMux * ogg_mux, GstOggPadData * oggpad)
Packit 971217
{
Packit 971217
  oggpad->packetno = 0;
Packit 971217
  oggpad->pageno = 0;
Packit 971217
  oggpad->eos = FALSE;
Packit 971217
Packit 971217
  /* we assume there will be some control data first for this pad */
Packit 971217
  oggpad->state = GST_OGG_PAD_STATE_CONTROL;
Packit 971217
  oggpad->new_page = TRUE;
Packit 971217
  oggpad->first_delta = FALSE;
Packit 971217
  oggpad->prev_delta = FALSE;
Packit 971217
  oggpad->data_pushed = FALSE;
Packit 971217
  oggpad->map.headers = NULL;
Packit 971217
  oggpad->map.queued = NULL;
Packit 971217
  oggpad->next_granule = 0;
Packit 971217
  oggpad->keyframe_granule = -1;
Packit 971217
  ogg_stream_clear (&oggpad->map.stream);
Packit 971217
  ogg_stream_init (&oggpad->map.stream, oggpad->map.serialno);
Packit 971217
Packit 971217
  if (oggpad->pagebuffers) {
Packit 971217
    GstBuffer *buf;
Packit 971217
Packit 971217
    while ((buf = g_queue_pop_head (oggpad->pagebuffers)) != NULL) {
Packit 971217
      gst_buffer_unref (buf);
Packit 971217
    }
Packit 971217
  } else if (GST_STATE (ogg_mux) > GST_STATE_READY) {
Packit 971217
    /* This will be initialized in init_collectpads when going from ready
Packit 971217
     * paused state */
Packit 971217
    oggpad->pagebuffers = g_queue_new ();
Packit 971217
  }
Packit 971217
Packit 971217
  gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
Packit 971217
}
Packit 971217
Packit 971217
static guint32
Packit 971217
gst_ogg_mux_generate_serialno (GstOggMux * ogg_mux)
Packit 971217
{
Packit 971217
  guint32 serialno;
Packit 971217
Packit 971217
  do {
Packit 971217
    serialno = g_random_int_range (0, G_MAXINT32);
Packit 971217
  } while (gst_ogg_mux_is_serialno_present (ogg_mux, serialno));
Packit 971217
Packit 971217
  return serialno;
Packit 971217
}
Packit 971217
Packit 971217
static GstPad *
Packit 971217
gst_ogg_mux_request_new_pad (GstElement * element,
Packit 971217
    GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
Packit 971217
{
Packit 971217
  GstOggMux *ogg_mux;
Packit 971217
  GstPad *newpad;
Packit 971217
  GstElementClass *klass;
Packit 971217
Packit 971217
  g_return_val_if_fail (templ != NULL, NULL);
Packit 971217
Packit 971217
  if (templ->direction != GST_PAD_SINK)
Packit 971217
    goto wrong_direction;
Packit 971217
Packit 971217
  g_return_val_if_fail (GST_IS_OGG_MUX (element), NULL);
Packit 971217
  ogg_mux = GST_OGG_MUX (element);
Packit 971217
Packit 971217
  klass = GST_ELEMENT_GET_CLASS (element);
Packit 971217
Packit 971217
  if (templ != gst_element_class_get_pad_template (klass, "video_%u") &&
Packit 971217
      templ != gst_element_class_get_pad_template (klass, "audio_%u") &&
Packit 971217
      templ != gst_element_class_get_pad_template (klass, "subtitle_%u")) {
Packit 971217
    goto wrong_template;
Packit 971217
  }
Packit 971217
Packit 971217
  {
Packit 971217
    guint32 serial;
Packit 971217
    gchar *name = NULL;
Packit 971217
Packit 971217
    if (req_name == NULL || strlen (req_name) < 6) {
Packit 971217
      /* no name given when requesting the pad, use random serial number */
Packit 971217
      serial = gst_ogg_mux_generate_serialno (ogg_mux);
Packit 971217
    } else {
Packit 971217
      /* parse serial number from requested padname */
Packit 971217
      unsigned long long_serial;
Packit 971217
      char *endptr = NULL;
Packit 971217
      long_serial = strtoul (&req_name[5], &endptr, 10);
Packit 971217
      if ((endptr && *endptr) || (long_serial & ~0xffffffff)) {
Packit 971217
        GST_WARNING_OBJECT (ogg_mux, "Invalid serial number specification: %s",
Packit 971217
            req_name + 5);
Packit 971217
        return NULL;
Packit 971217
      }
Packit 971217
      serial = (guint32) long_serial;
Packit 971217
    }
Packit 971217
    /* create new pad with the name */
Packit 971217
    GST_DEBUG_OBJECT (ogg_mux, "Creating new pad for serial %d", serial);
Packit 971217
Packit 971217
    if (templ == gst_element_class_get_pad_template (klass, "video_%u")) {
Packit 971217
      name = g_strdup_printf ("video_%u", serial);
Packit 971217
    } else if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) {
Packit 971217
      name = g_strdup_printf ("audio_%u", serial);
Packit 971217
    } else if (templ == gst_element_class_get_pad_template (klass,
Packit 971217
            "subtitle_%u")) {
Packit 971217
      name = g_strdup_printf ("subtitle_%u", serial);
Packit 971217
    }
Packit 971217
    newpad = gst_pad_new_from_template (templ, name);
Packit 971217
    g_free (name);
Packit 971217
Packit 971217
    /* construct our own wrapper data structure for the pad to
Packit 971217
     * keep track of its status */
Packit 971217
    {
Packit 971217
      GstOggPadData *oggpad;
Packit 971217
Packit 971217
      oggpad = (GstOggPadData *)
Packit 971217
          gst_collect_pads_add_pad (ogg_mux->collect, newpad,
Packit 971217
          sizeof (GstOggPadData), gst_ogg_mux_ogg_pad_destroy_notify, FALSE);
Packit 971217
      ogg_mux->active_pads++;
Packit 971217
Packit 971217
      oggpad->map.serialno = serial;
Packit 971217
      gst_ogg_pad_data_reset (ogg_mux, oggpad);
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  /* setup some pad functions */
Packit 971217
  gst_pad_set_link_function (newpad, gst_ogg_mux_sinkconnect);
Packit 971217
Packit 971217
  /* dd the pad to the element */
Packit 971217
  gst_element_add_pad (element, newpad);
Packit 971217
Packit 971217
  return newpad;
Packit 971217
Packit 971217
  /* ERRORS */
Packit 971217
wrong_direction:
Packit 971217
  {
Packit 971217
    g_warning ("ogg_mux: request pad that is not a SINK pad\n");
Packit 971217
    return NULL;
Packit 971217
  }
Packit 971217
wrong_template:
Packit 971217
  {
Packit 971217
    g_warning ("ogg_mux: this is not our template!\n");
Packit 971217
    return NULL;
Packit 971217
  }
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_release_pad (GstElement * element, GstPad * pad)
Packit 971217
{
Packit 971217
  GstOggMux *ogg_mux;
Packit 971217
Packit 971217
  ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));
Packit 971217
Packit 971217
  gst_collect_pads_remove_pad (ogg_mux->collect, pad);
Packit 971217
  gst_element_remove_pad (element, pad);
Packit 971217
Packit 971217
  gst_object_unref (ogg_mux);
Packit 971217
}
Packit 971217
Packit 971217
/* handle events */
Packit 971217
static gboolean
Packit 971217
gst_ogg_mux_handle_src_event (GstPad * pad, GstObject * parent,
Packit 971217
    GstEvent * event)
Packit 971217
{
Packit 971217
  gboolean res = FALSE;
Packit 971217
  GstOggMux *ogg_mux = GST_OGG_MUX (parent);
Packit 971217
Packit 971217
  switch (GST_EVENT_TYPE (event)) {
Packit 971217
    case GST_EVENT_SEEK:{
Packit 971217
      GstSeekFlags flags;
Packit 971217
Packit 971217
      gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
Packit 971217
      if (!ogg_mux->need_headers && (flags & GST_SEEK_FLAG_FLUSH) != 0) {
Packit 971217
        /* don't allow flushing seeks once we started */
Packit 971217
        gst_event_unref (event);
Packit 971217
        event = NULL;
Packit 971217
      }
Packit 971217
      break;
Packit 971217
    }
Packit 971217
    default:
Packit 971217
      break;
Packit 971217
  }
Packit 971217
Packit 971217
  if (event != NULL)
Packit 971217
    res = gst_pad_event_default (pad, parent, event);
Packit 971217
Packit 971217
  return res;
Packit 971217
}
Packit 971217
Packit 971217
static GstBuffer *
Packit 971217
gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
Packit 971217
{
Packit 971217
  GstBuffer *buffer;
Packit 971217
Packit 971217
  /* allocate space for header and body */
Packit 971217
  buffer = gst_buffer_new_and_alloc (page->header_len + page->body_len);
Packit 971217
  gst_buffer_fill (buffer, 0, page->header, page->header_len);
Packit 971217
  gst_buffer_fill (buffer, page->header_len, page->body, page->body_len);
Packit 971217
Packit 971217
  /* Here we set granulepos as our OFFSET_END to give easy direct access to
Packit 971217
   * this value later. Before we push it, we reset this to OFFSET + SIZE
Packit 971217
   * (see gst_ogg_mux_push_buffer). */
Packit 971217
  GST_BUFFER_OFFSET_END (buffer) = ogg_page_granulepos (page);
Packit 971217
  if (delta)
Packit 971217
    GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
Packit 971217
Packit 971217
  GST_LOG_OBJECT (mux, GST_GP_FORMAT
Packit 971217
      " created buffer %p from ogg page",
Packit 971217
      GST_GP_CAST (ogg_page_granulepos (page)), buffer);
Packit 971217
Packit 971217
  return buffer;
Packit 971217
}
Packit 971217
Packit 971217
static GstFlowReturn
Packit 971217
gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer,
Packit 971217
    GstOggPadData * oggpad)
Packit 971217
{
Packit 971217
  /* fix up OFFSET and OFFSET_END again */
Packit 971217
  GST_BUFFER_OFFSET (buffer) = mux->offset;
Packit 971217
  mux->offset += gst_buffer_get_size (buffer);
Packit 971217
  GST_BUFFER_OFFSET_END (buffer) = mux->offset;
Packit 971217
Packit 971217
  /* Ensure we have monotonically increasing timestamps in the output. */
Packit 971217
  if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
Packit 971217
    gint64 run_time = GST_BUFFER_TIMESTAMP (buffer);
Packit 971217
    if (mux->last_ts != GST_CLOCK_TIME_NONE && run_time < mux->last_ts)
Packit 971217
      GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts;
Packit 971217
    else
Packit 971217
      mux->last_ts = run_time;
Packit 971217
  }
Packit 971217
Packit 971217
  GST_LOG_OBJECT (mux->srcpad, "pushing %p, last_ts=%" GST_TIME_FORMAT,
Packit 971217
      buffer, GST_TIME_ARGS (mux->last_ts));
Packit 971217
Packit 971217
  return gst_pad_push (mux->srcpad, buffer);
Packit 971217
}
Packit 971217
Packit 971217
/* if all queues have at least one page, dequeue the page with the lowest
Packit 971217
 * timestamp */
Packit 971217
static gboolean
Packit 971217
gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret)
Packit 971217
{
Packit 971217
  GSList *walk;
Packit 971217
  GstOggPadData *opad = NULL;   /* "oldest" pad */
Packit 971217
  GstClockTime oldest = GST_CLOCK_TIME_NONE;
Packit 971217
  GstBuffer *buf = NULL;
Packit 971217
  gboolean ret = FALSE;
Packit 971217
Packit 971217
  *flowret = GST_FLOW_OK;
Packit 971217
Packit 971217
  walk = mux->collect->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *pad = (GstOggPadData *) walk->data;
Packit 971217
Packit 971217
    /* We need each queue to either be at EOS, or have one or more pages
Packit 971217
     * available with a set granulepos (i.e. not -1), otherwise we don't have
Packit 971217
     * enough data yet to determine which stream needs to go next for correct
Packit 971217
     * time ordering. */
Packit 971217
    if (pad->pagebuffers->length == 0) {
Packit 971217
      if (pad->eos) {
Packit 971217
        GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
            "pad is EOS, skipping for dequeue decision");
Packit 971217
      } else {
Packit 971217
        GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
            "no pages in this queue, can't dequeue");
Packit 971217
        return FALSE;
Packit 971217
      }
Packit 971217
    } else {
Packit 971217
      /* We then need to check for a non-negative granulepos */
Packit 971217
      gboolean valid = FALSE;
Packit 971217
      GList *l;
Packit 971217
Packit 971217
      for (l = pad->pagebuffers->head; l != NULL; l = l->next) {
Packit 971217
        buf = l->data;
Packit 971217
        /* Here we check the OFFSET_END, which is actually temporarily the
Packit 971217
         * granulepos value for this buffer */
Packit 971217
        if (GST_BUFFER_OFFSET_END_IS_VALID (buf)) {
Packit 971217
          valid = TRUE;
Packit 971217
          break;
Packit 971217
        }
Packit 971217
      }
Packit 971217
      if (!valid) {
Packit 971217
        GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
            "No page timestamps in queue, can't dequeue");
Packit 971217
        return FALSE;
Packit 971217
      }
Packit 971217
    }
Packit 971217
Packit 971217
    walk = g_slist_next (walk);
Packit 971217
  }
Packit 971217
Packit 971217
  walk = mux->collect->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *pad = (GstOggPadData *) walk->data;
Packit 971217
Packit 971217
    /* any page with a granulepos of -1 can be pushed immediately.
Packit 971217
     * TODO: it CAN be, but it seems silly to do so? */
Packit 971217
    buf = g_queue_peek_head (pad->pagebuffers);
Packit 971217
    while (buf && GST_BUFFER_OFFSET_END (buf) == -1) {
Packit 971217
      GST_LOG_OBJECT (pad->collect.pad, "[gp        -1] pushing page");
Packit 971217
      g_queue_pop_head (pad->pagebuffers);
Packit 971217
      *flowret = gst_ogg_mux_push_buffer (mux, buf, pad);
Packit 971217
      buf = g_queue_peek_head (pad->pagebuffers);
Packit 971217
      ret = TRUE;
Packit 971217
    }
Packit 971217
Packit 971217
    if (buf) {
Packit 971217
      /* if no oldest buffer yet, take this one */
Packit 971217
      if (oldest == GST_CLOCK_TIME_NONE) {
Packit 971217
        GST_LOG_OBJECT (mux, "no oldest yet, taking buffer %p from pad %"
Packit 971217
            GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT,
Packit 971217
            buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
Packit 971217
        oldest = GST_BUFFER_OFFSET (buf);
Packit 971217
        opad = pad;
Packit 971217
      } else {
Packit 971217
        /* if we have an oldest, compare with this one */
Packit 971217
        if (GST_BUFFER_OFFSET (buf) < oldest) {
Packit 971217
          GST_LOG_OBJECT (mux, "older buffer %p, taking from pad %"
Packit 971217
              GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT,
Packit 971217
              buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
Packit 971217
          oldest = GST_BUFFER_OFFSET (buf);
Packit 971217
          opad = pad;
Packit 971217
        }
Packit 971217
      }
Packit 971217
    }
Packit 971217
    walk = g_slist_next (walk);
Packit 971217
  }
Packit 971217
Packit 971217
  if (oldest != GST_CLOCK_TIME_NONE) {
Packit 971217
    g_assert (opad);
Packit 971217
    buf = g_queue_pop_head (opad->pagebuffers);
Packit 971217
    GST_LOG_OBJECT (opad->collect.pad,
Packit 971217
        GST_GP_FORMAT " pushing oldest page buffer %p (granulepos time %"
Packit 971217
        GST_TIME_FORMAT ")", GST_BUFFER_OFFSET_END (buf), buf,
Packit 971217
        GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
Packit 971217
    *flowret = gst_ogg_mux_push_buffer (mux, buf, opad);
Packit 971217
    ret = TRUE;
Packit 971217
  }
Packit 971217
Packit 971217
  return ret;
Packit 971217
}
Packit 971217
Packit 971217
/* put the given ogg page on a per-pad queue, timestamping it correctly.
Packit 971217
 * after that, dequeue and push as many pages as possible.
Packit 971217
 * Caller should make sure:
Packit 971217
 * pad->timestamp     was set with the timestamp of the first packet put
Packit 971217
 *                    on the page
Packit 971217
 * pad->timestamp_end was set with the timestamp + duration of the last packet
Packit 971217
 *                    put on the page
Packit 971217
 * pad->gp_time       was set with the time matching the gp of the last
Packit 971217
 *                    packet put on the page
Packit 971217
 *
Packit 971217
 * will also reset timestamp and timestamp_end, so caller func can restart
Packit 971217
 * counting.
Packit 971217
 */
Packit 971217
static GstFlowReturn
Packit 971217
gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPadData * pad,
Packit 971217
    ogg_page * page, gboolean delta)
Packit 971217
{
Packit 971217
  GstFlowReturn ret;
Packit 971217
  GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
Packit 971217
Packit 971217
  /* take the timestamp of the first packet on this page */
Packit 971217
  GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp;
Packit 971217
  GST_BUFFER_DURATION (buffer) = pad->timestamp_end - pad->timestamp;
Packit 971217
  /* take the gp time of the last completed packet on this page */
Packit 971217
  GST_BUFFER_OFFSET (buffer) = pad->gp_time;
Packit 971217
Packit 971217
  /* the next page will start where the current page's end time leaves off */
Packit 971217
  pad->timestamp = pad->timestamp_end;
Packit 971217
Packit 971217
  g_queue_push_tail (pad->pagebuffers, buffer);
Packit 971217
  GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
Packit 971217
      " queued buffer page %p (gp time %"
Packit 971217
      GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT
Packit 971217
      "), %d page buffers queued", GST_GP_CAST (ogg_page_granulepos (page)),
Packit 971217
      buffer, GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)),
Packit 971217
      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
Packit 971217
      g_queue_get_length (pad->pagebuffers));
Packit 971217
Packit 971217
  while (gst_ogg_mux_dequeue_page (mux, &ret)) {
Packit 971217
    if (ret != GST_FLOW_OK)
Packit 971217
      break;
Packit 971217
  }
Packit 971217
Packit 971217
  return ret;
Packit 971217
}
Packit 971217
Packit 971217
/*
Packit 971217
 * Given two pads, compare the buffers queued on it.
Packit 971217
 * Returns:
Packit 971217
 *  0 if they have an equal priority
Packit 971217
 * -1 if the first is better
Packit 971217
 *  1 if the second is better
Packit 971217
 * Priority decided by: a) validity, b) older timestamp, c) smaller number
Packit 971217
 * of muxed pages
Packit 971217
 */
Packit 971217
static gint
Packit 971217
gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPadData * first,
Packit 971217
    GstOggPadData * second)
Packit 971217
{
Packit 971217
  guint64 firsttime, secondtime;
Packit 971217
Packit 971217
  /* if the first pad doesn't contain anything or is even NULL, return
Packit 971217
   * the second pad as best candidate and vice versa */
Packit 971217
  if (first == NULL)
Packit 971217
    return 1;
Packit 971217
  if (second == NULL)
Packit 971217
    return -1;
Packit 971217
Packit 971217
  /* no timestamp on first buffer, it must go first */
Packit 971217
  firsttime = GST_BUFFER_TIMESTAMP (first->buffer);
Packit 971217
  if (firsttime == GST_CLOCK_TIME_NONE)
Packit 971217
    return -1;
Packit 971217
Packit 971217
  /* no timestamp on second buffer, it must go first */
Packit 971217
  secondtime = GST_BUFFER_TIMESTAMP (second->buffer);
Packit 971217
  if (secondtime == GST_CLOCK_TIME_NONE)
Packit 971217
    return 1;
Packit 971217
Packit 971217
  /* first buffer has higher timestamp, second one should go first */
Packit 971217
  if (secondtime < firsttime)
Packit 971217
    return 1;
Packit 971217
  /* second buffer has higher timestamp, first one should go first */
Packit 971217
  else if (secondtime > firsttime)
Packit 971217
    return -1;
Packit 971217
  else {
Packit 971217
    /* buffers with equal timestamps, prefer the pad that has the
Packit 971217
     * least number of pages muxed */
Packit 971217
    if (second->pageno < first->pageno)
Packit 971217
      return 1;
Packit 971217
    else if (second->pageno > first->pageno)
Packit 971217
      return -1;
Packit 971217
  }
Packit 971217
Packit 971217
  /* same priority if all of the above failed */
Packit 971217
  return 0;
Packit 971217
}
Packit 971217
Packit 971217
static GstBuffer *
Packit 971217
gst_ogg_mux_decorate_buffer (GstOggMux * ogg_mux, GstOggPadData * pad,
Packit 971217
    GstBuffer * buf)
Packit 971217
{
Packit 971217
  GstClockTime time, end_time;
Packit 971217
  gint64 duration, granule, limit;
Packit 971217
  GstClockTime next_time;
Packit 971217
  GstClockTimeDiff diff;
Packit 971217
  GstMapInfo map;
Packit 971217
  ogg_packet packet;
Packit 971217
  gboolean end_clip = TRUE;
Packit 971217
  GstAudioClippingMeta *meta;
Packit 971217
Packit 971217
  /* ensure messing with metadata is ok */
Packit 971217
  buf = gst_buffer_make_writable (buf);
Packit 971217
Packit 971217
  /* convert time to running time, so we need no longer bother about that */
Packit 971217
  time = GST_BUFFER_TIMESTAMP (buf);
Packit 971217
  if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (time))) {
Packit 971217
    time = gst_segment_to_running_time (&pad->segment, GST_FORMAT_TIME, time);
Packit 971217
    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) {
Packit 971217
      gst_buffer_unref (buf);
Packit 971217
      return NULL;
Packit 971217
    } else {
Packit 971217
      GST_BUFFER_TIMESTAMP (buf) = time;
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  /* now come up with granulepos stuff corresponding to time */
Packit 971217
  if (!pad->have_type ||
Packit 971217
      pad->map.granulerate_n <= 0 || pad->map.granulerate_d <= 0)
Packit 971217
    goto no_granule;
Packit 971217
Packit 971217
  gst_buffer_map (buf, &map, GST_MAP_READ);
Packit 971217
  packet.packet = map.data;
Packit 971217
  packet.bytes = map.size;
Packit 971217
Packit 971217
  gst_ogg_stream_update_stats (&pad->map, &packet);
Packit 971217
Packit 971217
  duration = gst_ogg_stream_get_packet_duration (&pad->map, &packet);
Packit 971217
Packit 971217
  gst_buffer_unmap (buf, &map);
Packit 971217
Packit 971217
  /* give up if no duration can be determined, relying on upstream */
Packit 971217
  if (G_UNLIKELY (duration < 0)) {
Packit 971217
    /* well, if some day we really could handle sparse input ... */
Packit 971217
    if (pad->map.is_sparse) {
Packit 971217
      limit = 1;
Packit 971217
      diff = 2;
Packit 971217
      goto resync;
Packit 971217
    }
Packit 971217
    GST_WARNING_OBJECT (pad->collect.pad,
Packit 971217
        "failed to determine packet duration");
Packit 971217
    goto no_granule;
Packit 971217
  }
Packit 971217
Packit 971217
  /* The last packet may have clipped samples. We need to test against
Packit 971217
   * the segment to ensure we do not use a granpos that encompasses those.
Packit 971217
   */
Packit 971217
  if (pad->map.audio_clipping) {
Packit 971217
    GstAudioClippingMeta *cmeta = gst_buffer_get_audio_clipping_meta (buf);
Packit 971217
Packit 971217
    g_assert (!cmeta || cmeta->format == GST_FORMAT_DEFAULT);
Packit 971217
    if (cmeta && cmeta->end && cmeta->end < duration) {
Packit 971217
      GST_DEBUG_OBJECT (pad->collect.pad,
Packit 971217
          "Clipping %" G_GUINT64_FORMAT " samples at the end", cmeta->end);
Packit 971217
      duration -= cmeta->end;
Packit 971217
      end_clip = FALSE;
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  if (end_clip) {
Packit 971217
    end_time =
Packit 971217
        gst_ogg_stream_granule_to_time (&pad->map,
Packit 971217
        pad->next_granule + duration);
Packit 971217
    meta = gst_buffer_get_audio_clipping_meta (buf);
Packit 971217
    if (meta && meta->end) {
Packit 971217
      if (meta->format == GST_FORMAT_DEFAULT) {
Packit 971217
        if (meta->end > duration) {
Packit 971217
          GST_WARNING_OBJECT (pad->collect.pad,
Packit 971217
              "Clip meta tries to clip more sample than exist in the buffer, clipping all");
Packit 971217
          duration = 0;
Packit 971217
        } else {
Packit 971217
          duration -= meta->end;
Packit 971217
        }
Packit 971217
      } else {
Packit 971217
        GST_WARNING_OBJECT (pad->collect.pad,
Packit 971217
            "Unsupported format in clip meta");
Packit 971217
      }
Packit 971217
    }
Packit 971217
    if (end_time > pad->segment.stop
Packit 971217
        && !GST_CLOCK_TIME_IS_VALID (gst_segment_to_running_time (&pad->segment,
Packit 971217
                GST_FORMAT_TIME, pad->segment.start + end_time))) {
Packit 971217
      gint64 actual_duration =
Packit 971217
          gst_util_uint64_scale_round (pad->segment.stop - time,
Packit 971217
          pad->map.granulerate_n,
Packit 971217
          GST_SECOND * pad->map.granulerate_d);
Packit 971217
      GST_INFO_OBJECT (ogg_mux,
Packit 971217
          "Got clipped last packet of duration %" G_GINT64_FORMAT " (%"
Packit 971217
          G_GINT64_FORMAT " clipped)", actual_duration,
Packit 971217
          duration - actual_duration);
Packit 971217
      duration = actual_duration;
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  GST_LOG_OBJECT (pad->collect.pad, "buffer ts %" GST_TIME_FORMAT
Packit 971217
      ", duration %" GST_TIME_FORMAT ", granule duration %" G_GINT64_FORMAT,
Packit 971217
      GST_TIME_ARGS (time), GST_TIME_ARGS (GST_BUFFER_DURATION (buf)),
Packit 971217
      duration);
Packit 971217
Packit 971217
  /* determine granule corresponding to time,
Packit 971217
   * using the inverse of oggdemux' granule -> time */
Packit 971217
Packit 971217
  /* see if interpolated granule matches good enough */
Packit 971217
  granule = pad->next_granule;
Packit 971217
  next_time = gst_ogg_stream_granule_to_time (&pad->map, pad->next_granule);
Packit 971217
  diff = GST_CLOCK_DIFF (next_time, time);
Packit 971217
Packit 971217
  /* we tolerate deviation up to configured or within granule granularity */
Packit 971217
  limit = gst_ogg_stream_granule_to_time (&pad->map, 1) / 2;
Packit 971217
  limit = MAX (limit, ogg_mux->max_tolerance);
Packit 971217
Packit 971217
  GST_LOG_OBJECT (pad->collect.pad, "expected granule %" G_GINT64_FORMAT " == "
Packit 971217
      "time %" GST_TIME_FORMAT " --> ts diff %" GST_STIME_FORMAT
Packit 971217
      " < tolerance %" GST_TIME_FORMAT " (?)",
Packit 971217
      granule, GST_TIME_ARGS (next_time), GST_STIME_ARGS (diff),
Packit 971217
      GST_TIME_ARGS (limit));
Packit 971217
Packit 971217
resync:
Packit 971217
  /* if not good enough, determine granule based on time */
Packit 971217
  if (diff > limit || diff < -limit) {
Packit 971217
    granule = gst_util_uint64_scale_round (time, pad->map.granulerate_n,
Packit 971217
        GST_SECOND * pad->map.granulerate_d);
Packit 971217
    GST_DEBUG_OBJECT (pad->collect.pad,
Packit 971217
        "resyncing to determined granule %" G_GINT64_FORMAT, granule);
Packit 971217
  }
Packit 971217
Packit 971217
  if (pad->map.is_ogm || pad->map.is_sparse) {
Packit 971217
    pad->next_granule = granule;
Packit 971217
  } else {
Packit 971217
    granule += duration;
Packit 971217
    pad->next_granule = granule;
Packit 971217
  }
Packit 971217
Packit 971217
  /* track previous keyframe */
Packit 971217
  if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
Packit 971217
    pad->keyframe_granule = granule;
Packit 971217
Packit 971217
  /* determine corresponding time and granulepos */
Packit 971217
  GST_BUFFER_OFFSET (buf) = gst_ogg_stream_granule_to_time (&pad->map, granule);
Packit 971217
  GST_BUFFER_OFFSET_END (buf) =
Packit 971217
      gst_ogg_stream_granule_to_granulepos (&pad->map, granule,
Packit 971217
      pad->keyframe_granule);
Packit 971217
Packit 971217
  GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
      GST_GP_FORMAT " decorated buffer %p (granulepos time %" GST_TIME_FORMAT
Packit 971217
      ")", GST_BUFFER_OFFSET_END (buf), buf,
Packit 971217
      GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
Packit 971217
Packit 971217
  return buf;
Packit 971217
Packit 971217
  /* ERRORS */
Packit 971217
no_granule:
Packit 971217
  {
Packit 971217
    GST_DEBUG_OBJECT (pad->collect.pad, "could not determine granulepos, "
Packit 971217
        "falling back to upstream provided metadata");
Packit 971217
    return buf;
Packit 971217
  }
Packit 971217
}
Packit 971217
Packit 971217
Packit 971217
/* make sure at least one buffer is queued on all pads, two if possible
Packit 971217
 *
Packit 971217
 * if pad->buffer == NULL, pad->next_buffer !=  NULL, then
Packit 971217
 *   we do not know if the buffer is the last or not
Packit 971217
 * if pad->buffer != NULL, pad->next_buffer != NULL, then
Packit 971217
 *   pad->buffer is not the last buffer for the pad
Packit 971217
 * if pad->buffer != NULL, pad->next_buffer == NULL, then
Packit 971217
 *   pad->buffer if the last buffer for the pad
Packit 971217
 *
Packit 971217
 * returns a pointer to an oggpad that holds the best buffer, or
Packit 971217
 * NULL when no pad was usable. "best" means the buffer marked
Packit 971217
 * with the lowest timestamp. If best->buffer == NULL then either
Packit 971217
 * we're at EOS (popped = FALSE), or a buffer got dropped, so retry. */
Packit 971217
static GstOggPadData *
Packit 971217
gst_ogg_mux_queue_pads (GstOggMux * ogg_mux, gboolean * popped)
Packit 971217
{
Packit 971217
  GstOggPadData *bestpad = NULL;
Packit 971217
  GSList *walk;
Packit 971217
Packit 971217
  *popped = FALSE;
Packit 971217
Packit 971217
  /* try to make sure we have a buffer from each usable pad first */
Packit 971217
  walk = ogg_mux->collect->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *pad;
Packit 971217
    GstCollectData *data;
Packit 971217
Packit 971217
    data = (GstCollectData *) walk->data;
Packit 971217
    pad = (GstOggPadData *) data;
Packit 971217
Packit 971217
    walk = g_slist_next (walk);
Packit 971217
Packit 971217
    GST_LOG_OBJECT (data->pad, "looking at pad for buffer");
Packit 971217
Packit 971217
    /* try to get a new buffer for this pad if needed and possible */
Packit 971217
    if (pad->buffer == NULL) {
Packit 971217
      GstBuffer *buf;
Packit 971217
Packit 971217
      buf = gst_collect_pads_pop (ogg_mux->collect, data);
Packit 971217
      GST_LOG_OBJECT (data->pad, "popped buffer %" GST_PTR_FORMAT, buf);
Packit 971217
Packit 971217
      /* On EOS we get a NULL buffer */
Packit 971217
      if (buf != NULL) {
Packit 971217
        *popped = TRUE;
Packit 971217
Packit 971217
        if (ogg_mux->delta_pad == NULL &&
Packit 971217
            GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
Packit 971217
          ogg_mux->delta_pad = pad;
Packit 971217
Packit 971217
        /* if we need headers */
Packit 971217
        if (pad->state == GST_OGG_PAD_STATE_CONTROL) {
Packit 971217
          /* and we have one */
Packit 971217
          ogg_packet packet;
Packit 971217
          gboolean is_header;
Packit 971217
          GstMapInfo map;
Packit 971217
Packit 971217
          gst_buffer_map (buf, &map, GST_MAP_READ);
Packit 971217
          packet.packet = map.data;
Packit 971217
          packet.bytes = map.size;
Packit 971217
Packit 971217
          /* if we're not yet in data mode, ensure we're setup on the first packet */
Packit 971217
          if (!pad->have_type) {
Packit 971217
            GstCaps *caps;
Packit 971217
Packit 971217
            /* Use headers in caps, if any; this will allow us to be resilient
Packit 971217
             * to starting streams on the fly, and some streams (like VP8
Packit 971217
             * at least) do not send headers packets, as other muxers don't
Packit 971217
             * expect/need them. */
Packit 971217
            caps = gst_pad_get_current_caps (GST_PAD_CAST (data->pad));
Packit 971217
            GST_DEBUG_OBJECT (data->pad, "checking caps: %" GST_PTR_FORMAT,
Packit 971217
                caps);
Packit 971217
Packit 971217
            pad->have_type =
Packit 971217
                gst_ogg_stream_setup_map_from_caps_headers (&pad->map, caps);
Packit 971217
Packit 971217
            if (!pad->have_type) {
Packit 971217
              /* fallback on the packet */
Packit 971217
              pad->have_type = gst_ogg_stream_setup_map (&pad->map, &packet);
Packit 971217
            }
Packit 971217
            if (!pad->have_type) {
Packit 971217
              /* fallback 2 to try to get the mapping from the caps */
Packit 971217
              pad->have_type =
Packit 971217
                  gst_ogg_stream_setup_map_from_caps (&pad->map, caps);
Packit 971217
            }
Packit 971217
            if (!pad->have_type) {
Packit 971217
              GST_ERROR_OBJECT (data->pad,
Packit 971217
                  "mapper didn't recognise input stream " "(pad caps: %"
Packit 971217
                  GST_PTR_FORMAT ")", caps);
Packit 971217
            } else {
Packit 971217
              GST_DEBUG_OBJECT (data->pad, "caps detected: %" GST_PTR_FORMAT,
Packit 971217
                  pad->map.caps);
Packit 971217
Packit 971217
              if (pad->map.is_sparse) {
Packit 971217
                GST_DEBUG_OBJECT (data->pad, "Pad is sparse, marking as such");
Packit 971217
                gst_collect_pads_set_waiting (ogg_mux->collect,
Packit 971217
                    (GstCollectData *) pad, FALSE);
Packit 971217
              }
Packit 971217
Packit 971217
              if (pad->map.is_video && ogg_mux->delta_pad == NULL) {
Packit 971217
                ogg_mux->delta_pad = pad;
Packit 971217
                GST_INFO_OBJECT (data->pad, "selected delta pad");
Packit 971217
              }
Packit 971217
            }
Packit 971217
            if (caps)
Packit 971217
              gst_caps_unref (caps);
Packit 971217
          }
Packit 971217
Packit 971217
          if (pad->have_type)
Packit 971217
            is_header = gst_ogg_stream_packet_is_header (&pad->map, &packet);
Packit 971217
          else                  /* fallback (FIXME 0.11: remove IN_CAPS hack) */
Packit 971217
            is_header = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_HEADER);
Packit 971217
Packit 971217
          gst_buffer_unmap (buf, &map);
Packit 971217
Packit 971217
          if (is_header) {
Packit 971217
            GST_DEBUG_OBJECT (ogg_mux,
Packit 971217
                "got header buffer in control state, ignoring");
Packit 971217
            /* just ignore */
Packit 971217
            pad->map.n_header_packets_seen++;
Packit 971217
            gst_buffer_unref (buf);
Packit 971217
            buf = NULL;
Packit 971217
          } else {
Packit 971217
            GST_DEBUG_OBJECT (ogg_mux,
Packit 971217
                "got data buffer in control state, switching to data mode");
Packit 971217
            /* this is a data buffer so switch to data state */
Packit 971217
            pad->state = GST_OGG_PAD_STATE_DATA;
Packit 971217
Packit 971217
            /* check if this type of stream allows generating granulepos
Packit 971217
             * metadata here, if not, upstream will have to provide */
Packit 971217
            if (gst_ogg_stream_granule_to_granulepos (&pad->map, 1, 1) < 0) {
Packit 971217
              GST_WARNING_OBJECT (data->pad, "can not generate metadata; "
Packit 971217
                  "relying on upstream");
Packit 971217
              /* disable metadata code path, otherwise not used anyway */
Packit 971217
              pad->map.granulerate_n = 0;
Packit 971217
            }
Packit 971217
          }
Packit 971217
        }
Packit 971217
Packit 971217
        /* so now we should have a real data packet;
Packit 971217
         * see that it is properly decorated */
Packit 971217
        if (G_LIKELY (buf)) {
Packit 971217
          buf = gst_ogg_mux_decorate_buffer (ogg_mux, pad, buf);
Packit 971217
          if (G_UNLIKELY (!buf))
Packit 971217
            GST_DEBUG_OBJECT (data->pad, "buffer clipped");
Packit 971217
        }
Packit 971217
      }
Packit 971217
Packit 971217
      pad->buffer = buf;
Packit 971217
    }
Packit 971217
Packit 971217
    /* we should have a buffer now, see if it is the best pad to
Packit 971217
     * pull on. Our best pad can't be eos */
Packit 971217
    if (pad->buffer && !pad->eos) {
Packit 971217
      if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) {
Packit 971217
        GST_LOG_OBJECT (data->pad,
Packit 971217
            "new best pad, with buffer %" GST_PTR_FORMAT, pad->buffer);
Packit 971217
Packit 971217
        bestpad = pad;
Packit 971217
      }
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  return bestpad;
Packit 971217
}
Packit 971217
Packit 971217
static GList *
Packit 971217
gst_ogg_mux_get_headers (GstOggPadData * pad)
Packit 971217
{
Packit 971217
  GList *res = NULL;
Packit 971217
  GstStructure *structure;
Packit 971217
  GstCaps *caps;
Packit 971217
  const GValue *streamheader;
Packit 971217
  GstPad *thepad;
Packit 971217
  GstBuffer *header;
Packit 971217
Packit 971217
  thepad = pad->collect.pad;
Packit 971217
Packit 971217
  GST_LOG_OBJECT (thepad, "getting headers");
Packit 971217
Packit 971217
  caps = gst_pad_get_current_caps (thepad);
Packit 971217
  if (caps == NULL) {
Packit 971217
    GST_INFO_OBJECT (thepad, "got empty caps as negotiated format");
Packit 971217
    return NULL;
Packit 971217
  }
Packit 971217
Packit 971217
  structure = gst_caps_get_structure (caps, 0);
Packit 971217
  streamheader = gst_structure_get_value (structure, "streamheader");
Packit 971217
  if (streamheader != NULL) {
Packit 971217
    GST_LOG_OBJECT (thepad, "got header");
Packit 971217
    if (G_VALUE_TYPE (streamheader) == GST_TYPE_ARRAY) {
Packit 971217
      GArray *bufarr = g_value_peek_pointer (streamheader);
Packit 971217
      gint i;
Packit 971217
Packit 971217
      GST_LOG_OBJECT (thepad, "got fixed list");
Packit 971217
Packit 971217
      for (i = 0; i < bufarr->len; i++) {
Packit 971217
        GValue *bufval = &g_array_index (bufarr, GValue, i);
Packit 971217
Packit 971217
        GST_LOG_OBJECT (thepad, "item %d", i);
Packit 971217
        if (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER) {
Packit 971217
          GstBuffer *buf = g_value_peek_pointer (bufval);
Packit 971217
Packit 971217
          GST_LOG_OBJECT (thepad, "adding item %d to header list", i);
Packit 971217
Packit 971217
          gst_buffer_ref (buf);
Packit 971217
          res = g_list_append (res, buf);
Packit 971217
        }
Packit 971217
      }
Packit 971217
    } else {
Packit 971217
      GST_LOG_OBJECT (thepad, "streamheader is not fixed list");
Packit 971217
    }
Packit 971217
Packit 971217
  } else if (gst_structure_has_name (structure, "video/x-dirac")) {
Packit 971217
    res = g_list_append (res, pad->buffer);
Packit 971217
    pad->buffer = NULL;
Packit 971217
  } else if (pad->have_type
Packit 971217
      && (header = gst_ogg_stream_get_headers (&pad->map))) {
Packit 971217
    res = g_list_append (res, header);
Packit 971217
  } else {
Packit 971217
    GST_LOG_OBJECT (thepad, "caps don't have streamheader");
Packit 971217
  }
Packit 971217
  gst_caps_unref (caps);
Packit 971217
Packit 971217
  return res;
Packit 971217
}
Packit 971217
Packit 971217
static GstCaps *
Packit 971217
gst_ogg_mux_set_header_on_caps (GstCaps * caps, GList * buffers)
Packit 971217
{
Packit 971217
  GstStructure *structure;
Packit 971217
  GValue array = { 0 };
Packit 971217
  GList *walk = buffers;
Packit 971217
Packit 971217
  caps = gst_caps_make_writable (caps);
Packit 971217
Packit 971217
  structure = gst_caps_get_structure (caps, 0);
Packit 971217
Packit 971217
  /* put buffers in a fixed list */
Packit 971217
  g_value_init (&array, GST_TYPE_ARRAY);
Packit 971217
Packit 971217
  while (walk) {
Packit 971217
    GstBuffer *buf = GST_BUFFER (walk->data);
Packit 971217
    GValue value = { 0 };
Packit 971217
Packit 971217
    walk = walk->next;
Packit 971217
Packit 971217
    /* mark buffer */
Packit 971217
    GST_LOG ("Setting HEADER on buffer of length %" G_GSIZE_FORMAT,
Packit 971217
        gst_buffer_get_size (buf));
Packit 971217
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
Packit 971217
Packit 971217
    g_value_init (&value, GST_TYPE_BUFFER);
Packit 971217
    gst_value_set_buffer (&value, buf);
Packit 971217
    gst_value_array_append_value (&array, &value);
Packit 971217
    g_value_unset (&value);
Packit 971217
  }
Packit 971217
  gst_structure_take_value (structure, "streamheader", &array);
Packit 971217
Packit 971217
  return caps;
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_create_header_packet_with_flags (ogg_packet * packet,
Packit 971217
    gboolean bos, gboolean eos)
Packit 971217
{
Packit 971217
  packet->granulepos = 0;
Packit 971217
  /* mark BOS and packet number */
Packit 971217
  packet->b_o_s = bos;
Packit 971217
  /* mark EOS */
Packit 971217
  packet->e_o_s = eos;
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_create_header_packet (ogg_packet * packet, GstOggPadData * pad)
Packit 971217
{
Packit 971217
  gst_ogg_mux_create_header_packet_with_flags (packet, pad->packetno == 0, 0);
Packit 971217
  packet->packetno = pad->packetno++;
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_submit_skeleton_header_packet (GstOggMux * mux,
Packit 971217
    ogg_stream_state * os, GstBuffer * buf, gboolean bos, gboolean eos)
Packit 971217
{
Packit 971217
  ogg_packet packet;
Packit 971217
  GstMapInfo map;
Packit 971217
Packit 971217
  gst_buffer_map (buf, &map, GST_MAP_READ);
Packit 971217
  packet.packet = map.data;
Packit 971217
  packet.bytes = map.size;
Packit 971217
  gst_ogg_mux_create_header_packet_with_flags (&packet, bos, eos);
Packit 971217
  ogg_stream_packetin (os, &packet);
Packit 971217
  gst_buffer_unmap (buf, &map);
Packit 971217
  gst_buffer_unref (buf);
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_make_fishead (GstOggMux * mux, ogg_stream_state * os)
Packit 971217
{
Packit 971217
  GstByteWriter bw;
Packit 971217
  GstBuffer *fishead;
Packit 971217
  gboolean handled = TRUE;
Packit 971217
Packit 971217
  GST_DEBUG_OBJECT (mux, "Creating fishead");
Packit 971217
Packit 971217
  gst_byte_writer_init_with_size (&bw, 64, TRUE);
Packit 971217
  handled &= gst_byte_writer_put_string_utf8 (&bw, "fishead");
Packit 971217
  handled &= gst_byte_writer_put_int16_le (&bw, 3);     /* version major */
Packit 971217
  handled &= gst_byte_writer_put_int16_le (&bw, 0);     /* version minor */
Packit 971217
  handled &= gst_byte_writer_put_int64_le (&bw, 0);     /* presentation time numerator */
Packit 971217
  handled &= gst_byte_writer_put_int64_le (&bw, 1000);  /* ...and denominator */
Packit 971217
  handled &= gst_byte_writer_put_int64_le (&bw, 0);     /* base time numerator */
Packit 971217
  handled &= gst_byte_writer_put_int64_le (&bw, 1000);  /* ...and denominator */
Packit 971217
  handled &= gst_byte_writer_fill (&bw, ' ', 20);       /* UTC time */
Packit 971217
  g_assert (handled && gst_byte_writer_get_pos (&bw) == 64);
Packit 971217
  fishead = gst_byte_writer_reset_and_get_buffer (&bw;;
Packit 971217
  gst_ogg_mux_submit_skeleton_header_packet (mux, os, fishead, 1, 0);
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_byte_writer_put_string_utf8 (GstByteWriter * bw, const char *s)
Packit 971217
{
Packit 971217
  if (!gst_byte_writer_put_data (bw, (const guint8 *) s, strlen (s)))
Packit 971217
    GST_ERROR ("put_data failed");
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_add_fisbone_message_header (GstOggMux * mux, GstByteWriter * bw,
Packit 971217
    const char *tag, const char *value)
Packit 971217
{
Packit 971217
  /* It is valid to pass NULL as the value to omit the tag */
Packit 971217
  if (!value)
Packit 971217
    return;
Packit 971217
  GST_DEBUG_OBJECT (mux, "Adding fisbone message header %s: %s", tag, value);
Packit 971217
  gst_ogg_mux_byte_writer_put_string_utf8 (bw, tag);
Packit 971217
  gst_ogg_mux_byte_writer_put_string_utf8 (bw, ": ");
Packit 971217
  gst_ogg_mux_byte_writer_put_string_utf8 (bw, value);
Packit 971217
  gst_ogg_mux_byte_writer_put_string_utf8 (bw, "\r\n");
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_add_fisbone_message_header_from_tags (GstOggMux * mux,
Packit 971217
    GstByteWriter * bw, const char *header, const char *tag,
Packit 971217
    const GstTagList * tags)
Packit 971217
{
Packit 971217
  GString *s;
Packit 971217
  guint size = gst_tag_list_get_tag_size (tags, tag), n;
Packit 971217
  GST_DEBUG_OBJECT (mux, "Found %u tags for name %s", size, tag);
Packit 971217
  if (size == 0)
Packit 971217
    return;
Packit 971217
  s = g_string_new ("");
Packit 971217
  for (n = 0; n < size; ++n) {
Packit 971217
    gchar *tmp;
Packit 971217
    if (n)
Packit 971217
      g_string_append (s, ", ");
Packit 971217
    if (gst_tag_list_get_string_index (tags, tag, n, &tmp)) {
Packit 971217
      g_string_append (s, tmp);
Packit 971217
      g_free (tmp);
Packit 971217
    } else {
Packit 971217
      GST_WARNING_OBJECT (mux, "Tag %s index %u was not found (%u total)", tag,
Packit 971217
          n, size);
Packit 971217
    }
Packit 971217
  }
Packit 971217
  gst_ogg_mux_add_fisbone_message_header (mux, bw, header, s->str);
Packit 971217
  g_string_free (s, TRUE);
Packit 971217
}
Packit 971217
Packit 971217
/* This is a basic placeholder to generate roles for the tracks.
Packit 971217
   For tracks with more than one video, both video tracks will get
Packit 971217
   tagged with a "video/main" role, but we have no way of knowing
Packit 971217
   which one is the main one, if any. We could just pick one. For
Packit 971217
   audio, it's more complicated as we don't know which is music,
Packit 971217
   which is dubbing, etc. For kate, we could take a pretty good
Packit 971217
   guess based on the category, as role essentially is category.
Packit 971217
   For now, leave this as is. */
Packit 971217
static const char *
Packit 971217
gst_ogg_mux_get_default_role (GstOggPadData * pad)
Packit 971217
{
Packit 971217
  const char *type = gst_ogg_stream_get_media_type (&pad->map);
Packit 971217
  if (type) {
Packit 971217
    if (!strncmp (type, "video/", strlen ("video/")))
Packit 971217
      return "video/main";
Packit 971217
    if (!strncmp (type, "audio/", strlen ("audio/")))
Packit 971217
      return "audio/main";
Packit 971217
    if (!strcmp (type + strlen (type) - strlen ("kate"), "kate"))
Packit 971217
      return "text/caption";
Packit 971217
  }
Packit 971217
  return NULL;
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_make_fisbone (GstOggMux * mux, ogg_stream_state * os,
Packit 971217
    GstOggPadData * pad)
Packit 971217
{
Packit 971217
  GstByteWriter bw;
Packit 971217
  gboolean handled = TRUE;
Packit 971217
Packit 971217
  GST_DEBUG_OBJECT (mux,
Packit 971217
      "Creating %s fisbone for serial %08x",
Packit 971217
      gst_ogg_stream_get_media_type (&pad->map), pad->map.serialno);
Packit 971217
Packit 971217
  gst_byte_writer_init (&bw;;
Packit 971217
  handled &= gst_byte_writer_put_string_utf8 (&bw, "fisbone");
Packit 971217
  handled &= gst_byte_writer_put_int32_le (&bw, 44);    /* offset to message headers */
Packit 971217
  handled &= gst_byte_writer_put_uint32_le (&bw, pad->map.serialno);
Packit 971217
  handled &= gst_byte_writer_put_uint32_le (&bw, pad->map.n_header_packets);
Packit 971217
  handled &= gst_byte_writer_put_uint64_le (&bw, pad->map.granulerate_n);
Packit 971217
  handled &= gst_byte_writer_put_uint64_le (&bw, pad->map.granulerate_d);
Packit 971217
  handled &= gst_byte_writer_put_uint64_le (&bw, 0);    /* base granule */
Packit 971217
  handled &= gst_byte_writer_put_uint32_le (&bw, pad->map.preroll);
Packit 971217
  handled &= gst_byte_writer_put_uint8 (&bw, pad->map.granuleshift);
Packit 971217
  handled &= gst_byte_writer_fill (&bw, 0, 3);  /* padding */
Packit 971217
  /* message header fields - MIME type for now */
Packit 971217
  gst_ogg_mux_add_fisbone_message_header (mux, &bw, "Content-Type",
Packit 971217
      gst_ogg_stream_get_media_type (&pad->map));
Packit 971217
  gst_ogg_mux_add_fisbone_message_header (mux, &bw, "Role",
Packit 971217
      gst_ogg_mux_get_default_role (pad));
Packit 971217
  gst_ogg_mux_add_fisbone_message_header_from_tags (mux, &bw, "Language",
Packit 971217
      GST_TAG_LANGUAGE_CODE, pad->tags);
Packit 971217
  gst_ogg_mux_add_fisbone_message_header_from_tags (mux, &bw, "Title",
Packit 971217
      GST_TAG_TITLE, pad->tags);
Packit 971217
Packit 971217
  if (G_UNLIKELY (!handled))
Packit 971217
    GST_WARNING_OBJECT (mux, "Error writing fishbon");
Packit 971217
Packit 971217
  gst_ogg_mux_submit_skeleton_header_packet (mux, os,
Packit 971217
      gst_byte_writer_reset_and_get_buffer (&bw), 0, 0);
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_make_fistail (GstOggMux * mux, ogg_stream_state * os)
Packit 971217
{
Packit 971217
  GST_DEBUG_OBJECT (mux, "Creating fistail");
Packit 971217
Packit 971217
  gst_ogg_mux_submit_skeleton_header_packet (mux, os,
Packit 971217
      gst_buffer_new_and_alloc (0), 0, 1);
Packit 971217
}
Packit 971217
Packit 971217
/*
Packit 971217
 * For each pad we need to write out one (small) header in one
Packit 971217
 * page that allows decoders to identify the type of the stream.
Packit 971217
 * After that we need to write out all extra info for the decoders.
Packit 971217
 * In the case of a codec that also needs data as configuration, we can
Packit 971217
 * find that info in the streamcaps.
Packit 971217
 * After writing the headers we must start a new page for the data.
Packit 971217
 */
Packit 971217
static GstFlowReturn
Packit 971217
gst_ogg_mux_send_headers (GstOggMux * mux)
Packit 971217
{
Packit 971217
  GSList *walk;
Packit 971217
  GList *hbufs, *hwalk;
Packit 971217
  GstCaps *caps;
Packit 971217
  GstFlowReturn ret;
Packit 971217
  ogg_page page;
Packit 971217
  ogg_stream_state skeleton_stream;
Packit 971217
Packit 971217
  hbufs = NULL;
Packit 971217
  ret = GST_FLOW_OK;
Packit 971217
Packit 971217
  GST_LOG_OBJECT (mux, "collecting headers");
Packit 971217
Packit 971217
  walk = mux->collect->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *pad;
Packit 971217
    GstPad *thepad;
Packit 971217
Packit 971217
    pad = (GstOggPadData *) walk->data;
Packit 971217
    thepad = pad->collect.pad;
Packit 971217
Packit 971217
    walk = g_slist_next (walk);
Packit 971217
Packit 971217
    GST_LOG_OBJECT (mux, "looking at pad %s:%s", GST_DEBUG_PAD_NAME (thepad));
Packit 971217
Packit 971217
    /* if the pad has no buffer and is not sparse, we don't care */
Packit 971217
    if (pad->buffer == NULL && !pad->map.is_sparse)
Packit 971217
      continue;
Packit 971217
Packit 971217
    /* now figure out the headers */
Packit 971217
    pad->map.headers = gst_ogg_mux_get_headers (pad);
Packit 971217
  }
Packit 971217
Packit 971217
  GST_LOG_OBJECT (mux, "creating BOS pages");
Packit 971217
  walk = mux->collect->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *pad;
Packit 971217
    GstBuffer *buf;
Packit 971217
    ogg_packet packet;
Packit 971217
    GstPad *thepad;
Packit 971217
    GstBuffer *hbuf;
Packit 971217
    GstMapInfo map;
Packit 971217
    GstCaps *caps;
Packit 971217
    const gchar *mime_type = "";
Packit 971217
Packit 971217
    pad = (GstOggPadData *) walk->data;
Packit 971217
    thepad = pad->collect.pad;
Packit 971217
    walk = walk->next;
Packit 971217
Packit 971217
    pad->packetno = 0;
Packit 971217
Packit 971217
    GST_LOG_OBJECT (thepad, "looping over headers");
Packit 971217
Packit 971217
    if (pad->map.headers) {
Packit 971217
      buf = GST_BUFFER (pad->map.headers->data);
Packit 971217
      pad->map.headers = g_list_remove (pad->map.headers, buf);
Packit 971217
    } else if (pad->buffer) {
Packit 971217
      buf = pad->buffer;
Packit 971217
      gst_buffer_ref (buf);
Packit 971217
    } else {
Packit 971217
      /* fixme -- should be caught in the previous list traversal. */
Packit 971217
      GST_OBJECT_LOCK (thepad);
Packit 971217
      g_critical ("No headers or buffers on pad %s:%s",
Packit 971217
          GST_DEBUG_PAD_NAME (thepad));
Packit 971217
      GST_OBJECT_UNLOCK (thepad);
Packit 971217
      continue;
Packit 971217
    }
Packit 971217
Packit 971217
    if ((caps = gst_pad_get_current_caps (thepad))) {
Packit 971217
      GstStructure *structure = gst_caps_get_structure (caps, 0);
Packit 971217
      mime_type = gst_structure_get_name (structure);
Packit 971217
    } else {
Packit 971217
      GST_INFO_OBJECT (thepad, "got empty caps as negotiated format");
Packit 971217
    }
Packit 971217
Packit 971217
    /* create a packet from the buffer */
Packit 971217
    gst_buffer_map (buf, &map, GST_MAP_READ);
Packit 971217
    packet.packet = map.data;
Packit 971217
    packet.bytes = map.size;
Packit 971217
Packit 971217
    gst_ogg_mux_create_header_packet (&packet, pad);
Packit 971217
Packit 971217
    /* swap the packet in */
Packit 971217
    ogg_stream_packetin (&pad->map.stream, &packet);
Packit 971217
Packit 971217
    gst_buffer_unmap (buf, &map);
Packit 971217
    gst_buffer_unref (buf);
Packit 971217
Packit 971217
    GST_LOG_OBJECT (thepad, "flushing out BOS page");
Packit 971217
    if (!ogg_stream_flush (&pad->map.stream, &page))
Packit 971217
      g_critical ("Could not flush BOS page");
Packit 971217
Packit 971217
    hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
Packit 971217
Packit 971217
    GST_LOG_OBJECT (mux, "swapped out page with mime type '%s'", mime_type);
Packit 971217
Packit 971217
    /* quick hack: put video pages at the front.
Packit 971217
     * Ideally, we would have a settable enum for which Ogg
Packit 971217
     * profile we work with, and order based on that.
Packit 971217
     * (FIXME: if there is more than one video stream, shouldn't we only put
Packit 971217
     * one's BOS into the first page, followed by an audio stream's BOS, and
Packit 971217
     * only then followed by the remaining video and audio streams?) */
Packit 971217
    if (pad->map.is_video) {
Packit 971217
      GST_DEBUG_OBJECT (thepad, "putting %s page at the front", mime_type);
Packit 971217
      hbufs = g_list_prepend (hbufs, hbuf);
Packit 971217
    } else {
Packit 971217
      hbufs = g_list_append (hbufs, hbuf);
Packit 971217
    }
Packit 971217
Packit 971217
    if (caps) {
Packit 971217
      gst_caps_unref (caps);
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  /* The Skeleton BOS goes first - even before the video that went first before */
Packit 971217
  if (mux->use_skeleton) {
Packit 971217
    ogg_stream_init (&skeleton_stream, gst_ogg_mux_generate_serialno (mux));
Packit 971217
    gst_ogg_mux_make_fishead (mux, &skeleton_stream);
Packit 971217
    while (ogg_stream_flush (&skeleton_stream, &page) > 0) {
Packit 971217
      GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
Packit 971217
      hbufs = g_list_append (hbufs, hbuf);
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  GST_LOG_OBJECT (mux, "creating next headers");
Packit 971217
  walk = mux->collect->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *pad;
Packit 971217
    GstPad *thepad;
Packit 971217
Packit 971217
    pad = (GstOggPadData *) walk->data;
Packit 971217
    thepad = pad->collect.pad;
Packit 971217
Packit 971217
    walk = walk->next;
Packit 971217
Packit 971217
    if (mux->use_skeleton)
Packit 971217
      gst_ogg_mux_make_fisbone (mux, &skeleton_stream, pad);
Packit 971217
Packit 971217
    GST_LOG_OBJECT (mux, "looping over headers for pad %s:%s",
Packit 971217
        GST_DEBUG_PAD_NAME (thepad));
Packit 971217
Packit 971217
    hwalk = pad->map.headers;
Packit 971217
    while (hwalk) {
Packit 971217
      GstBuffer *buf = GST_BUFFER (hwalk->data);
Packit 971217
      ogg_packet packet;
Packit 971217
      ogg_page page;
Packit 971217
      GstMapInfo map;
Packit 971217
Packit 971217
      hwalk = hwalk->next;
Packit 971217
Packit 971217
      /* create a packet from the buffer */
Packit 971217
      gst_buffer_map (buf, &map, GST_MAP_READ);
Packit 971217
      packet.packet = map.data;
Packit 971217
      packet.bytes = map.size;
Packit 971217
Packit 971217
      gst_ogg_mux_create_header_packet (&packet, pad);
Packit 971217
Packit 971217
      /* swap the packet in */
Packit 971217
      ogg_stream_packetin (&pad->map.stream, &packet);
Packit 971217
      gst_buffer_unmap (buf, &map);
Packit 971217
      gst_buffer_unref (buf);
Packit 971217
Packit 971217
      /* if last header, flush page */
Packit 971217
      if (hwalk == NULL) {
Packit 971217
        GST_LOG_OBJECT (mux,
Packit 971217
            "flushing page as packet %" G_GUINT64_FORMAT " is first or "
Packit 971217
            "last packet", (guint64) packet.packetno);
Packit 971217
        while (ogg_stream_flush (&pad->map.stream, &page)) {
Packit 971217
          GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
Packit 971217
Packit 971217
          GST_LOG_OBJECT (mux, "swapped out page");
Packit 971217
          hbufs = g_list_append (hbufs, hbuf);
Packit 971217
        }
Packit 971217
      } else {
Packit 971217
        GST_LOG_OBJECT (mux, "try to swap out page");
Packit 971217
        /* just try to swap out a page then */
Packit 971217
        while (ogg_stream_pageout (&pad->map.stream, &page) > 0) {
Packit 971217
          GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
Packit 971217
Packit 971217
          GST_LOG_OBJECT (mux, "swapped out page");
Packit 971217
          hbufs = g_list_append (hbufs, hbuf);
Packit 971217
        }
Packit 971217
      }
Packit 971217
    }
Packit 971217
    g_list_free (pad->map.headers);
Packit 971217
    pad->map.headers = NULL;
Packit 971217
  }
Packit 971217
Packit 971217
  if (mux->use_skeleton) {
Packit 971217
    /* flush accumulated fisbones, the fistail must be on a separate page */
Packit 971217
    while (ogg_stream_flush (&skeleton_stream, &page) > 0) {
Packit 971217
      GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
Packit 971217
      hbufs = g_list_append (hbufs, hbuf);
Packit 971217
    }
Packit 971217
    gst_ogg_mux_make_fistail (mux, &skeleton_stream);
Packit 971217
    while (ogg_stream_flush (&skeleton_stream, &page) > 0) {
Packit 971217
      GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
Packit 971217
      hbufs = g_list_append (hbufs, hbuf);
Packit 971217
    }
Packit 971217
    ogg_stream_clear (&skeleton_stream);
Packit 971217
  }
Packit 971217
Packit 971217
  /* hbufs holds all buffers for the headers now */
Packit 971217
Packit 971217
  /* create caps with the buffers */
Packit 971217
  /* FIXME: should prefer media type audio/ogg, video/ogg, etc. depending on
Packit 971217
   * what we create, if acceptable downstream (instead of defaulting to
Packit 971217
   * application/ogg because that's the first in the template caps) */
Packit 971217
  caps = gst_pad_get_allowed_caps (mux->srcpad);
Packit 971217
  if (caps) {
Packit 971217
    if (!gst_caps_is_fixed (caps))
Packit 971217
      caps = gst_caps_fixate (caps);
Packit 971217
  }
Packit 971217
  if (!caps)
Packit 971217
    caps = gst_caps_new_empty_simple ("application/ogg");
Packit 971217
Packit 971217
  caps = gst_ogg_mux_set_header_on_caps (caps, hbufs);
Packit 971217
  gst_pad_set_caps (mux->srcpad, caps);
Packit 971217
  gst_caps_unref (caps);
Packit 971217
Packit 971217
  /* Send segment event */
Packit 971217
  {
Packit 971217
    GstSegment segment;
Packit 971217
    gst_segment_init (&segment, GST_FORMAT_TIME);
Packit 971217
    gst_pad_push_event (mux->srcpad, gst_event_new_segment (&segment));
Packit 971217
  }
Packit 971217
Packit 971217
  /* and send the buffers */
Packit 971217
  while (hbufs != NULL) {
Packit 971217
    GstBuffer *buf = GST_BUFFER (hbufs->data);
Packit 971217
Packit 971217
    hbufs = g_list_delete_link (hbufs, hbufs);
Packit 971217
Packit 971217
    if ((ret = gst_ogg_mux_push_buffer (mux, buf, NULL)) != GST_FLOW_OK)
Packit 971217
      break;
Packit 971217
  }
Packit 971217
  /* free any remaining nodes/buffers in case we couldn't push them */
Packit 971217
  g_list_foreach (hbufs, (GFunc) gst_mini_object_unref, NULL);
Packit 971217
  g_list_free (hbufs);
Packit 971217
Packit 971217
  return ret;
Packit 971217
}
Packit 971217
Packit 971217
/* this function is called to process data on the best pending pad.
Packit 971217
 *
Packit 971217
 * basic idea:
Packit 971217
 *
Packit 971217
 * 1) store the selected pad and keep on pulling until we fill a
Packit 971217
 *    complete ogg page or the ogg page is filled above the max-delay
Packit 971217
 *    threshold. This is needed because the ogg spec says that
Packit 971217
 *    you should fill a complete page with data from the same logical
Packit 971217
 *    stream. When the page is filled, go back to 1).
Packit 971217
 * 2) before filling a page, read ahead one more buffer to see if this
Packit 971217
 *    packet is the last of the stream. We need to do this because the ogg
Packit 971217
 *    spec mandates that the last packet should have the EOS flag set before
Packit 971217
 *    sending it to ogg. if pad->buffer is NULL we need to wait to find out
Packit 971217
 *    whether there are any more buffers.
Packit 971217
 * 3) pages get queued on a per-pad queue. Every time a page is queued, a
Packit 971217
 *    dequeue is called, which will dequeue the oldest page on any pad, provided
Packit 971217
 *    that ALL pads have at least one marked page in the queue (or remaining
Packit 971217
 *    pads are at EOS)
Packit 971217
 */
Packit 971217
static GstFlowReturn
Packit 971217
gst_ogg_mux_process_best_pad (GstOggMux * ogg_mux, GstOggPadData * best)
Packit 971217
{
Packit 971217
  GstFlowReturn ret = GST_FLOW_OK;
Packit 971217
  gboolean delta_unit;
Packit 971217
  gint64 granulepos = 0;
Packit 971217
  GstClockTime timestamp, gp_time;
Packit 971217
  GstBuffer *next_buf;
Packit 971217
Packit 971217
  GST_LOG_OBJECT (ogg_mux, "best pad %" GST_PTR_FORMAT
Packit 971217
      ", currently pulling from %" GST_PTR_FORMAT, best->collect.pad,
Packit 971217
      ogg_mux->pulling ? ogg_mux->pulling->collect.pad : NULL);
Packit 971217
Packit 971217
  if (ogg_mux->pulling) {
Packit 971217
    next_buf = gst_collect_pads_peek (ogg_mux->collect,
Packit 971217
        &ogg_mux->pulling->collect);
Packit 971217
    if (next_buf) {
Packit 971217
      ogg_mux->pulling->eos = FALSE;
Packit 971217
      gst_buffer_unref (next_buf);
Packit 971217
    } else if (!ogg_mux->pulling->map.is_sparse) {
Packit 971217
      GST_DEBUG_OBJECT (ogg_mux->pulling->collect.pad, "setting eos to true");
Packit 971217
      ogg_mux->pulling->eos = TRUE;
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  /* We could end up pushing from the best pad instead, so check that
Packit 971217
   * as well */
Packit 971217
  if (best && best != ogg_mux->pulling) {
Packit 971217
    next_buf = gst_collect_pads_peek (ogg_mux->collect, &best->collect);
Packit 971217
    if (next_buf) {
Packit 971217
      best->eos = FALSE;
Packit 971217
      gst_buffer_unref (next_buf);
Packit 971217
    } else if (!best->map.is_sparse) {
Packit 971217
      GST_DEBUG_OBJECT (best->collect.pad, "setting eos to true");
Packit 971217
      best->eos = TRUE;
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  /* if we were already pulling from one pad, but the new "best" buffer is
Packit 971217
   * from another pad, we need to check if we have reason to flush a page
Packit 971217
   * for the pad we were pulling from before */
Packit 971217
  if (ogg_mux->pulling && best &&
Packit 971217
      ogg_mux->pulling != best && ogg_mux->pulling->buffer) {
Packit 971217
    GstOggPadData *pad = ogg_mux->pulling;
Packit 971217
    GstClockTime last_ts = GST_BUFFER_END_TIME (pad->buffer);
Packit 971217
Packit 971217
    /* if the next packet in the current page is going to make the page
Packit 971217
     * too long, we need to flush */
Packit 971217
    if (last_ts > ogg_mux->next_ts + ogg_mux->max_delay) {
Packit 971217
      ogg_page page;
Packit 971217
Packit 971217
      GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
          GST_GP_FORMAT " stored packet %" G_GINT64_FORMAT
Packit 971217
          " will make page too long, flushing",
Packit 971217
          GST_BUFFER_OFFSET_END (pad->buffer),
Packit 971217
          (gint64) pad->map.stream.packetno);
Packit 971217
Packit 971217
      while (ogg_stream_flush (&pad->map.stream, &page)) {
Packit 971217
        /* end time of this page is the timestamp of the next buffer */
Packit 971217
        ogg_mux->pulling->timestamp_end = GST_BUFFER_TIMESTAMP (pad->buffer);
Packit 971217
        /* Place page into the per-pad queue */
Packit 971217
        ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
Packit 971217
            pad->first_delta);
Packit 971217
        /* increment the page number counter */
Packit 971217
        pad->pageno++;
Packit 971217
        /* mark other pages as delta */
Packit 971217
        pad->first_delta = TRUE;
Packit 971217
      }
Packit 971217
      pad->new_page = TRUE;
Packit 971217
      ogg_mux->pulling = NULL;
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  /* if we don't know which pad to pull on, use the best one */
Packit 971217
  if (ogg_mux->pulling == NULL) {
Packit 971217
    ogg_mux->pulling = best;
Packit 971217
    GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "pulling from best pad");
Packit 971217
Packit 971217
    /* remember timestamp and gp time of first buffer for this new pad */
Packit 971217
    if (ogg_mux->pulling != NULL) {
Packit 971217
      ogg_mux->next_ts = GST_BUFFER_TIMESTAMP (ogg_mux->pulling->buffer);
Packit 971217
      GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "updated times, next ts %"
Packit 971217
          GST_TIME_FORMAT, GST_TIME_ARGS (ogg_mux->next_ts));
Packit 971217
    } else {
Packit 971217
      GST_LOG_OBJECT (ogg_mux->srcpad, "sending EOS");
Packit 971217
      /* no pad to pull on, send EOS */
Packit 971217
      gst_pad_push_event (ogg_mux->srcpad, gst_event_new_eos ());
Packit 971217
      return GST_FLOW_FLUSHING;
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  if (ogg_mux->need_headers) {
Packit 971217
    ret = gst_ogg_mux_send_headers (ogg_mux);
Packit 971217
    ogg_mux->need_headers = FALSE;
Packit 971217
  }
Packit 971217
Packit 971217
  /* we are pulling from a pad, continue to do so until a page
Packit 971217
   * has been filled and queued */
Packit 971217
  if (ogg_mux->pulling != NULL) {
Packit 971217
    ogg_packet packet;
Packit 971217
    ogg_page page;
Packit 971217
    GstBuffer *buf, *tmpbuf;
Packit 971217
    GstOggPadData *pad = ogg_mux->pulling;
Packit 971217
    gint64 duration;
Packit 971217
    gboolean force_flush;
Packit 971217
    GstMapInfo map;
Packit 971217
Packit 971217
    GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "pulling from pad");
Packit 971217
Packit 971217
    /* now see if we have a buffer */
Packit 971217
    buf = pad->buffer;
Packit 971217
    if (buf == NULL) {
Packit 971217
      GST_DEBUG_OBJECT (ogg_mux, "pad was EOS");
Packit 971217
      ogg_mux->pulling = NULL;
Packit 971217
      return GST_FLOW_OK;
Packit 971217
    }
Packit 971217
Packit 971217
    delta_unit = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
Packit 971217
    duration = GST_BUFFER_DURATION (buf);
Packit 971217
Packit 971217
    /* if the current "next timestamp" on the pad is unset, then this is the
Packit 971217
     * first packet on the new page.  Update our pad's page timestamp */
Packit 971217
    if (ogg_mux->pulling->timestamp == GST_CLOCK_TIME_NONE) {
Packit 971217
      ogg_mux->pulling->timestamp = GST_BUFFER_TIMESTAMP (buf);
Packit 971217
      GST_LOG_OBJECT (ogg_mux->pulling->collect.pad,
Packit 971217
          "updated pad timestamp to %" GST_TIME_FORMAT,
Packit 971217
          GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
Packit 971217
    }
Packit 971217
    /* create a packet from the buffer */
Packit 971217
    gst_buffer_map (buf, &map, GST_MAP_READ);
Packit 971217
    packet.packet = map.data;
Packit 971217
    packet.bytes = map.size;
Packit 971217
    packet.granulepos = GST_BUFFER_OFFSET_END (buf);
Packit 971217
    if (packet.granulepos == -1)
Packit 971217
      packet.granulepos = 0;
Packit 971217
    /* mark BOS and packet number */
Packit 971217
    packet.b_o_s = (pad->packetno == 0);
Packit 971217
    packet.packetno = pad->packetno++;
Packit 971217
    GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
Packit 971217
        " packet %" G_GINT64_FORMAT " (%ld bytes) created from buffer",
Packit 971217
        GST_GP_CAST (packet.granulepos), (gint64) packet.packetno,
Packit 971217
        packet.bytes);
Packit 971217
Packit 971217
    packet.e_o_s = ogg_mux->pulling->eos ? 1 : 0;
Packit 971217
    tmpbuf = NULL;
Packit 971217
Packit 971217
    /* we flush when we see a new keyframe */
Packit 971217
    force_flush = (pad->prev_delta && !delta_unit)
Packit 971217
        || pad->map.always_flush_page;
Packit 971217
    if (duration != -1) {
Packit 971217
      pad->duration += duration;
Packit 971217
      /* if page duration exceeds max, flush page */
Packit 971217
      if (pad->duration > ogg_mux->max_page_delay) {
Packit 971217
        force_flush = TRUE;
Packit 971217
        pad->duration = 0;
Packit 971217
      }
Packit 971217
    }
Packit 971217
Packit 971217
    if (GST_BUFFER_IS_DISCONT (buf)) {
Packit 971217
      if (pad->data_pushed) {
Packit 971217
        GST_LOG_OBJECT (pad->collect.pad, "got discont");
Packit 971217
        packet.packetno++;
Packit 971217
        /* No public API for this; hack things in */
Packit 971217
        pad->map.stream.pageno++;
Packit 971217
        force_flush = TRUE;
Packit 971217
      } else {
Packit 971217
        GST_LOG_OBJECT (pad->collect.pad, "discont at stream start");
Packit 971217
      }
Packit 971217
    }
Packit 971217
Packit 971217
    /* flush the currently built page if necessary */
Packit 971217
    if (force_flush) {
Packit 971217
      GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
          GST_GP_FORMAT " forced flush of page before this packet",
Packit 971217
          GST_BUFFER_OFFSET_END (pad->buffer));
Packit 971217
      while (ogg_stream_flush (&pad->map.stream, &page)) {
Packit 971217
        /* end time of this page is the timestamp of the next buffer */
Packit 971217
        ogg_mux->pulling->timestamp_end = GST_BUFFER_TIMESTAMP (pad->buffer);
Packit 971217
        ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
Packit 971217
            pad->first_delta);
Packit 971217
Packit 971217
        /* increment the page number counter */
Packit 971217
        pad->pageno++;
Packit 971217
        /* mark other pages as delta */
Packit 971217
        pad->first_delta = TRUE;
Packit 971217
      }
Packit 971217
      pad->new_page = TRUE;
Packit 971217
    }
Packit 971217
Packit 971217
    /* if this is the first packet of a new page figure out the delta flag */
Packit 971217
    if (pad->new_page) {
Packit 971217
      if (delta_unit) {
Packit 971217
        /* mark the page as delta */
Packit 971217
        pad->first_delta = TRUE;
Packit 971217
      } else {
Packit 971217
        /* got a keyframe */
Packit 971217
        if (ogg_mux->delta_pad == pad) {
Packit 971217
          /* if we get it on the pad with deltaunits,
Packit 971217
           * we mark the page as non delta */
Packit 971217
          pad->first_delta = FALSE;
Packit 971217
        } else if (ogg_mux->delta_pad != NULL) {
Packit 971217
          /* if there are pads with delta frames, we
Packit 971217
           * must mark this one as delta */
Packit 971217
          pad->first_delta = TRUE;
Packit 971217
        } else {
Packit 971217
          pad->first_delta = FALSE;
Packit 971217
        }
Packit 971217
      }
Packit 971217
      pad->new_page = FALSE;
Packit 971217
    }
Packit 971217
Packit 971217
    /* save key unit to track delta->key unit transitions */
Packit 971217
    pad->prev_delta = delta_unit;
Packit 971217
Packit 971217
    /* swap the packet in */
Packit 971217
    if (packet.e_o_s == 1)
Packit 971217
      GST_DEBUG_OBJECT (pad->collect.pad, "swapping in EOS packet");
Packit 971217
    if (packet.b_o_s == 1)
Packit 971217
      GST_DEBUG_OBJECT (pad->collect.pad, "swapping in BOS packet");
Packit 971217
Packit 971217
    ogg_stream_packetin (&pad->map.stream, &packet);
Packit 971217
    gst_buffer_unmap (buf, &map);
Packit 971217
    pad->data_pushed = TRUE;
Packit 971217
Packit 971217
    gp_time = GST_BUFFER_OFFSET (pad->buffer);
Packit 971217
    granulepos = GST_BUFFER_OFFSET_END (pad->buffer);
Packit 971217
    timestamp = GST_BUFFER_TIMESTAMP (pad->buffer);
Packit 971217
Packit 971217
    GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
        GST_GP_FORMAT " packet %" G_GINT64_FORMAT ", gp time %"
Packit 971217
        GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT " packetin'd",
Packit 971217
        granulepos, (gint64) packet.packetno, GST_TIME_ARGS (gp_time),
Packit 971217
        GST_TIME_ARGS (timestamp));
Packit 971217
    /* don't need the old buffer anymore */
Packit 971217
    gst_buffer_unref (pad->buffer);
Packit 971217
    /* store new readahead buffer */
Packit 971217
    pad->buffer = tmpbuf;
Packit 971217
Packit 971217
    /* let ogg write out the pages now. The packet we got could end
Packit 971217
     * up in more than one page so we need to write them all */
Packit 971217
    if (ogg_stream_pageout (&pad->map.stream, &page) > 0) {
Packit 971217
      /* we have a new page, so we need to timestamp it correctly.
Packit 971217
       * if this fresh packet ends on this page, then the page's granulepos
Packit 971217
       * comes from that packet, and we should set this buffer's timestamp */
Packit 971217
Packit 971217
      GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
          GST_GP_FORMAT " packet %" G_GINT64_FORMAT ", time %"
Packit 971217
          GST_TIME_FORMAT ") caused new page",
Packit 971217
          granulepos, (gint64) packet.packetno, GST_TIME_ARGS (timestamp));
Packit 971217
      GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
          GST_GP_FORMAT " new page %ld",
Packit 971217
          GST_GP_CAST (ogg_page_granulepos (&page)), pad->map.stream.pageno);
Packit 971217
Packit 971217
      if (ogg_page_granulepos (&page) == granulepos) {
Packit 971217
        /* the packet we streamed in finishes on the current page,
Packit 971217
         * because the page's granulepos is the granulepos of the last
Packit 971217
         * packet completed on that page,
Packit 971217
         * so update the timestamp that we will give to the page */
Packit 971217
        GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
            GST_GP_FORMAT
Packit 971217
            " packet finishes on current page, updating gp time to %"
Packit 971217
            GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (gp_time));
Packit 971217
        pad->gp_time = gp_time;
Packit 971217
      } else {
Packit 971217
        GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
            GST_GP_FORMAT
Packit 971217
            " packet spans beyond current page, keeping old gp time %"
Packit 971217
            GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (pad->gp_time));
Packit 971217
      }
Packit 971217
Packit 971217
      /* push the page */
Packit 971217
      /* end time of this page is the timestamp of the next buffer */
Packit 971217
      pad->timestamp_end = timestamp;
Packit 971217
      ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page, pad->first_delta);
Packit 971217
      pad->pageno++;
Packit 971217
      /* mark next pages as delta */
Packit 971217
      pad->first_delta = TRUE;
Packit 971217
Packit 971217
      /* use an inner loop here to flush the remaining pages and
Packit 971217
       * mark them as delta frames as well */
Packit 971217
      while (ogg_stream_pageout (&pad->map.stream, &page) > 0) {
Packit 971217
        if (ogg_page_granulepos (&page) == granulepos) {
Packit 971217
          /* the page has taken up the new packet completely, which means
Packit 971217
           * the packet ends the page and we can update the gp time
Packit 971217
           * before pushing out */
Packit 971217
          pad->gp_time = gp_time;
Packit 971217
        }
Packit 971217
Packit 971217
        /* we have a complete page now, we can push the page
Packit 971217
         * and make sure to pull on a new pad the next time around */
Packit 971217
        ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
Packit 971217
            pad->first_delta);
Packit 971217
        /* increment the page number counter */
Packit 971217
        pad->pageno++;
Packit 971217
      }
Packit 971217
      /* need a new page as well */
Packit 971217
      pad->new_page = TRUE;
Packit 971217
      pad->duration = 0;
Packit 971217
      /* we're done pulling on this pad, make sure to choose a new
Packit 971217
       * pad for pulling in the next iteration */
Packit 971217
      ogg_mux->pulling = NULL;
Packit 971217
    }
Packit 971217
Packit 971217
    /* Update the gp time, if necessary, since any future page will have at
Packit 971217
     * least this gp time.
Packit 971217
     */
Packit 971217
    if (pad->gp_time < gp_time) {
Packit 971217
      pad->gp_time = gp_time;
Packit 971217
      GST_LOG_OBJECT (pad->collect.pad,
Packit 971217
          "Updated running gp time of pad %" GST_PTR_FORMAT
Packit 971217
          " to %" GST_TIME_FORMAT, pad->collect.pad, GST_TIME_ARGS (gp_time));
Packit 971217
    }
Packit 971217
  }
Packit 971217
Packit 971217
  return ret;
Packit 971217
}
Packit 971217
Packit 971217
/* all_pads_eos:
Packit 971217
 *
Packit 971217
 * Checks if all pads are EOS'd by peeking.
Packit 971217
 *
Packit 971217
 * Returns TRUE if all pads are EOS.
Packit 971217
 */
Packit 971217
static gboolean
Packit 971217
all_pads_eos (GstCollectPads * pads)
Packit 971217
{
Packit 971217
  GSList *walk;
Packit 971217
Packit 971217
  walk = pads->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *oggpad = (GstOggPadData *) walk->data;
Packit 971217
Packit 971217
    GST_DEBUG_OBJECT (oggpad->collect.pad,
Packit 971217
        "oggpad %p eos %d", oggpad, oggpad->eos);
Packit 971217
Packit 971217
    if (!oggpad->eos)
Packit 971217
      return FALSE;
Packit 971217
Packit 971217
    walk = g_slist_next (walk);
Packit 971217
  }
Packit 971217
Packit 971217
  return TRUE;
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_send_start_events (GstOggMux * ogg_mux, GstCollectPads * pads)
Packit 971217
{
Packit 971217
  gchar s_id[32];
Packit 971217
Packit 971217
  /* stream-start (FIXME: create id based on input ids) and
Packit 971217
   * also do something with the group id */
Packit 971217
  g_snprintf (s_id, sizeof (s_id), "oggmux-%08x", g_random_int ());
Packit 971217
  gst_pad_push_event (ogg_mux->srcpad, gst_event_new_stream_start (s_id));
Packit 971217
Packit 971217
  /* we'll send caps later, need to collect all headers first */
Packit 971217
}
Packit 971217
Packit 971217
/* This function is called when there is data on all pads.
Packit 971217
 *
Packit 971217
 * It finds a pad to pull on, this is done by looking at the buffers
Packit 971217
 * to decide which one to use, and using the 'oldest' one first. It then calls
Packit 971217
 * gst_ogg_mux_process_best_pad() to process as much data as possible.
Packit 971217
 *
Packit 971217
 * If all the pads have received EOS, it flushes out all data by continually
Packit 971217
 * getting the best pad and calling gst_ogg_mux_process_best_pad() until they
Packit 971217
 * are all empty, and then sends EOS.
Packit 971217
 */
Packit 971217
static GstFlowReturn
Packit 971217
gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
Packit 971217
{
Packit 971217
  GstOggPadData *best;
Packit 971217
  GstFlowReturn ret;
Packit 971217
  gboolean popped;
Packit 971217
Packit 971217
  GST_LOG_OBJECT (ogg_mux, "collected");
Packit 971217
Packit 971217
  if (ogg_mux->need_start_events) {
Packit 971217
    gst_ogg_mux_send_start_events (ogg_mux, pads);
Packit 971217
    ogg_mux->need_start_events = FALSE;
Packit 971217
  }
Packit 971217
Packit 971217
  /* queue buffers on all pads; find a buffer with the lowest timestamp */
Packit 971217
  best = gst_ogg_mux_queue_pads (ogg_mux, &popped);
Packit 971217
Packit 971217
  if (popped)
Packit 971217
    return GST_FLOW_OK;
Packit 971217
Packit 971217
  if (best == NULL) {
Packit 971217
    /* No data, assume EOS */
Packit 971217
    goto eos;
Packit 971217
  }
Packit 971217
Packit 971217
  /* This is not supposed to happen */
Packit 971217
  g_return_val_if_fail (best->buffer != NULL, GST_FLOW_ERROR);
Packit 971217
Packit 971217
  ret = gst_ogg_mux_process_best_pad (ogg_mux, best);
Packit 971217
Packit 971217
  if (best->eos && all_pads_eos (pads))
Packit 971217
    goto eos;
Packit 971217
Packit 971217
  /* We might have used up a cached pad->buffer. If all streams
Packit 971217
   * have a buffer ready in collectpads, collectpads will block at
Packit 971217
   * next chain, and will never call collected again. So we make a
Packit 971217
   * last call to _queue_pads now, to ensure that collectpads can
Packit 971217
   * push to at least one pad (mostly for streams with a single
Packit 971217
   * logical stream). */
Packit 971217
  gst_ogg_mux_queue_pads (ogg_mux, &popped);
Packit 971217
Packit 971217
  return ret;
Packit 971217
Packit 971217
eos:
Packit 971217
  {
Packit 971217
    GST_DEBUG_OBJECT (ogg_mux, "no data available, must be EOS");
Packit 971217
    gst_pad_push_event (ogg_mux->srcpad, gst_event_new_eos ());
Packit 971217
    return GST_FLOW_EOS;
Packit 971217
  }
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_get_property (GObject * object,
Packit 971217
    guint prop_id, GValue * value, GParamSpec * pspec)
Packit 971217
{
Packit 971217
  GstOggMux *ogg_mux;
Packit 971217
Packit 971217
  ogg_mux = GST_OGG_MUX (object);
Packit 971217
Packit 971217
  switch (prop_id) {
Packit 971217
    case ARG_MAX_DELAY:
Packit 971217
      g_value_set_uint64 (value, ogg_mux->max_delay);
Packit 971217
      break;
Packit 971217
    case ARG_MAX_PAGE_DELAY:
Packit 971217
      g_value_set_uint64 (value, ogg_mux->max_page_delay);
Packit 971217
      break;
Packit 971217
    case ARG_MAX_TOLERANCE:
Packit 971217
      g_value_set_uint64 (value, ogg_mux->max_tolerance);
Packit 971217
      break;
Packit 971217
    case ARG_SKELETON:
Packit 971217
      g_value_set_boolean (value, ogg_mux->use_skeleton);
Packit 971217
      break;
Packit 971217
    default:
Packit 971217
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit 971217
      break;
Packit 971217
  }
Packit 971217
}
Packit 971217
Packit 971217
static void
Packit 971217
gst_ogg_mux_set_property (GObject * object,
Packit 971217
    guint prop_id, const GValue * value, GParamSpec * pspec)
Packit 971217
{
Packit 971217
  GstOggMux *ogg_mux;
Packit 971217
Packit 971217
  ogg_mux = GST_OGG_MUX (object);
Packit 971217
Packit 971217
  switch (prop_id) {
Packit 971217
    case ARG_MAX_DELAY:
Packit 971217
      ogg_mux->max_delay = g_value_get_uint64 (value);
Packit 971217
      break;
Packit 971217
    case ARG_MAX_PAGE_DELAY:
Packit 971217
      ogg_mux->max_page_delay = g_value_get_uint64 (value);
Packit 971217
      break;
Packit 971217
    case ARG_MAX_TOLERANCE:
Packit 971217
      ogg_mux->max_tolerance = g_value_get_uint64 (value);
Packit 971217
      break;
Packit 971217
    case ARG_SKELETON:
Packit 971217
      ogg_mux->use_skeleton = g_value_get_boolean (value);
Packit 971217
      break;
Packit 971217
    default:
Packit 971217
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit 971217
      break;
Packit 971217
  }
Packit 971217
}
Packit 971217
Packit 971217
/* reset all variables in the ogg pads. */
Packit 971217
static void
Packit 971217
gst_ogg_mux_init_collectpads (GstCollectPads * collect)
Packit 971217
{
Packit 971217
  GSList *walk;
Packit 971217
Packit 971217
  walk = collect->data;
Packit 971217
  while (walk) {
Packit 971217
    GstOggPadData *oggpad = (GstOggPadData *) walk->data;
Packit 971217
Packit 971217
    ogg_stream_clear (&oggpad->map.stream);
Packit 971217
    ogg_stream_init (&oggpad->map.stream, oggpad->map.serialno);
Packit 971217
    oggpad->packetno = 0;
Packit 971217
    oggpad->pageno = 0;
Packit 971217
    oggpad->eos = FALSE;
Packit 971217
    /* we assume there will be some control data first for this pad */
Packit 971217
    oggpad->state = GST_OGG_PAD_STATE_CONTROL;
Packit 971217
    oggpad->new_page = TRUE;
Packit 971217
    oggpad->first_delta = FALSE;
Packit 971217
    oggpad->prev_delta = FALSE;
Packit 971217
    oggpad->data_pushed = FALSE;
Packit 971217
    oggpad->pagebuffers = g_queue_new ();
Packit 971217
Packit 971217
    gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
Packit 971217
Packit 971217
    walk = g_slist_next (walk);
Packit 971217
  }
Packit 971217
}
Packit 971217
Packit 971217
/* Clear all buffers from the collectpads object */
Packit 971217
static void
Packit 971217
gst_ogg_mux_clear_collectpads (GstCollectPads * collect)
Packit 971217
{
Packit 971217
  GSList *walk;
Packit 971217
Packit 971217
  for (walk = collect->data; walk; walk = g_slist_next (walk)) {
Packit 971217
    GstOggPadData *oggpad = (GstOggPadData *) walk->data;
Packit 971217
    GstBuffer *buf;
Packit 971217
Packit 971217
    ogg_stream_clear (&oggpad->map.stream);
Packit 971217
Packit 971217
    while ((buf = g_queue_pop_head (oggpad->pagebuffers)) != NULL) {
Packit 971217
      GST_LOG ("flushing buffer : %p", buf);
Packit 971217
      gst_buffer_unref (buf);
Packit 971217
    }
Packit 971217
    g_queue_free (oggpad->pagebuffers);
Packit 971217
    oggpad->pagebuffers = NULL;
Packit 971217
Packit 971217
    if (oggpad->buffer) {
Packit 971217
      gst_buffer_unref (oggpad->buffer);
Packit 971217
      oggpad->buffer = NULL;
Packit 971217
    }
Packit 971217
Packit 971217
    if (oggpad->tags) {
Packit 971217
      gst_tag_list_unref (oggpad->tags);
Packit 971217
      oggpad->tags = NULL;
Packit 971217
    }
Packit 971217
Packit 971217
    gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
Packit 971217
  }
Packit 971217
}
Packit 971217
Packit 971217
static GstStateChangeReturn
Packit 971217
gst_ogg_mux_change_state (GstElement * element, GstStateChange transition)
Packit 971217
{
Packit 971217
  GstOggMux *ogg_mux;
Packit 971217
  GstStateChangeReturn ret;
Packit 971217
Packit 971217
  ogg_mux = GST_OGG_MUX (element);
Packit 971217
Packit 971217
  switch (transition) {
Packit 971217
    case GST_STATE_CHANGE_NULL_TO_READY:
Packit 971217
      break;
Packit 971217
    case GST_STATE_CHANGE_READY_TO_PAUSED:
Packit 971217
      gst_ogg_mux_clear (ogg_mux);
Packit 971217
      gst_ogg_mux_init_collectpads (ogg_mux->collect);
Packit 971217
      gst_collect_pads_start (ogg_mux->collect);
Packit 971217
      break;
Packit 971217
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
Packit 971217
      break;
Packit 971217
    case GST_STATE_CHANGE_PAUSED_TO_READY:
Packit 971217
      gst_collect_pads_stop (ogg_mux->collect);
Packit 971217
      break;
Packit 971217
    default:
Packit 971217
      break;
Packit 971217
  }
Packit 971217
Packit 971217
  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
Packit 971217
Packit 971217
  switch (transition) {
Packit 971217
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
Packit 971217
      break;
Packit 971217
    case GST_STATE_CHANGE_PAUSED_TO_READY:
Packit 971217
      gst_ogg_mux_clear_collectpads (ogg_mux->collect);
Packit 971217
      break;
Packit 971217
    case GST_STATE_CHANGE_READY_TO_NULL:
Packit 971217
      break;
Packit 971217
    default:
Packit 971217
      break;
Packit 971217
  }
Packit 971217
Packit 971217
  return ret;
Packit 971217
}
Packit 971217
Packit 971217
gboolean
Packit 971217
gst_ogg_mux_plugin_init (GstPlugin * plugin)
Packit 971217
{
Packit 971217
  GST_DEBUG_CATEGORY_INIT (gst_ogg_mux_debug, "oggmux", 0, "ogg muxer");
Packit 971217
Packit 971217
  return gst_element_register (plugin, "oggmux", GST_RANK_PRIMARY,
Packit 971217
      GST_TYPE_OGG_MUX);
Packit 971217
}