Added gst-plugins-base-subtitles0.10-0.10.34 for Meego Harmattan 1.2
[mafwsubrenderer] / gst-plugins-base-subtitles0.10 / ext / theora / gsttheoraenc.c
diff --git a/gst-plugins-base-subtitles0.10/ext/theora/gsttheoraenc.c b/gst-plugins-base-subtitles0.10/ext/theora/gsttheoraenc.c
new file mode 100644 (file)
index 0000000..c580675
--- /dev/null
@@ -0,0 +1,1565 @@
+/* GStreamer
+ * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-theoraenc
+ * @see_also: theoradec, oggmux
+ *
+ * This element encodes raw video into a Theora stream.
+ * <ulink url="http://www.theora.org/">Theora</ulink> is a royalty-free
+ * video codec maintained by the <ulink url="http://www.xiph.org/">Xiph.org
+ * Foundation</ulink>, based on the VP3 codec.
+ *
+ * The theora codec internally only supports encoding of images that are a
+ * multiple of 16 pixels in both X and Y direction. It is however perfectly
+ * possible to encode images with other dimensions because an arbitrary
+ * rectangular cropping region can be set up. This element will automatically
+ * set up a correct cropping region if the dimensions are not multiples of 16
+ * pixels.
+ *
+ * To control the quality of the encoding, the #GstTheoraEnc::bitrate and
+ * #GstTheoraEnc::quality properties can be used. These two properties are
+ * mutualy exclusive. Setting the bitrate property will produce a constant
+ * bitrate (CBR) stream while setting the quality property will produce a
+ * variable bitrate (VBR) stream.
+ *
+ * <refsect2>
+ * <title>Example pipeline</title>
+ * |[
+ * gst-launch -v videotestsrc num-buffers=1000 ! theoraenc ! oggmux ! filesink location=videotestsrc.ogg
+ * ]| This example pipeline will encode a test video source to theora muxed in an
+ * ogg container. Refer to the theoradec documentation to decode the create
+ * stream.
+ * </refsect2>
+ *
+ * Last reviewed on 2006-03-01 (0.10.4)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gsttheoraenc.h"
+
+#include <string.h>
+#include <stdlib.h>             /* free */
+
+#include <gst/tag/tag.h>
+#include <gst/video/video.h>
+
+#define GST_CAT_DEFAULT theoraenc_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+#define GST_TYPE_BORDER_MODE (gst_border_mode_get_type())
+static GType
+gst_border_mode_get_type (void)
+{
+  static GType border_mode_type = 0;
+  static const GEnumValue border_mode[] = {
+    {BORDER_NONE, "No Border", "none"},
+    {BORDER_BLACK, "Black Border", "black"},
+    {BORDER_MIRROR, "Mirror image in borders", "mirror"},
+    {0, NULL, NULL},
+  };
+
+  if (!border_mode_type) {
+    border_mode_type =
+        g_enum_register_static ("GstTheoraEncBorderMode", border_mode);
+  }
+  return border_mode_type;
+}
+
+#define GST_TYPE_MULTIPASS_MODE (gst_multipass_mode_get_type())
+static GType
+gst_multipass_mode_get_type (void)
+{
+  static GType multipass_mode_type = 0;
+  static const GEnumValue multipass_mode[] = {
+    {MULTIPASS_MODE_SINGLE_PASS, "Single pass", "single-pass"},
+    {MULTIPASS_MODE_FIRST_PASS, "First pass", "first-pass"},
+    {MULTIPASS_MODE_SECOND_PASS, "Second pass", "second-pass"},
+    {0, NULL, NULL},
+  };
+
+  if (!multipass_mode_type) {
+    multipass_mode_type =
+        g_enum_register_static ("GstTheoraEncMultipassMode", multipass_mode);
+  }
+  return multipass_mode_type;
+}
+
+/* taken from theora/lib/toplevel.c */
+static int
+_ilog (unsigned int v)
+{
+  int ret = 0;
+
+  while (v) {
+    ret++;
+    v >>= 1;
+  }
+  return (ret);
+}
+
+#define THEORA_DEF_BITRATE              0
+#define THEORA_DEF_QUALITY              48
+#define THEORA_DEF_KEYFRAME_AUTO        TRUE
+#define THEORA_DEF_KEYFRAME_FREQ        64
+#define THEORA_DEF_KEYFRAME_FREQ_FORCE  64
+#define THEORA_DEF_SPEEDLEVEL           1
+#define THEORA_DEF_VP3_COMPATIBLE       FALSE
+#define THEORA_DEF_DROP_FRAMES          TRUE
+#define THEORA_DEF_CAP_OVERFLOW         TRUE
+#define THEORA_DEF_CAP_UNDERFLOW        FALSE
+#define THEORA_DEF_RATE_BUFFER          0
+#define THEORA_DEF_MULTIPASS_CACHE_FILE NULL
+#define THEORA_DEF_MULTIPASS_MODE       MULTIPASS_MODE_SINGLE_PASS
+enum
+{
+  PROP_0,
+  PROP_CENTER,
+  PROP_BORDER,
+  PROP_BITRATE,
+  PROP_QUALITY,
+  PROP_QUICK,
+  PROP_KEYFRAME_AUTO,
+  PROP_KEYFRAME_FREQ,
+  PROP_KEYFRAME_FREQ_FORCE,
+  PROP_KEYFRAME_THRESHOLD,
+  PROP_KEYFRAME_MINDISTANCE,
+  PROP_NOISE_SENSITIVITY,
+  PROP_SHARPNESS,
+  PROP_SPEEDLEVEL,
+  PROP_VP3_COMPATIBLE,
+  PROP_DROP_FRAMES,
+  PROP_CAP_OVERFLOW,
+  PROP_CAP_UNDERFLOW,
+  PROP_RATE_BUFFER,
+  PROP_MULTIPASS_CACHE_FILE,
+  PROP_MULTIPASS_MODE
+      /* FILL ME */
+};
+
+/* this function does a straight granulepos -> timestamp conversion */
+static GstClockTime
+granulepos_to_timestamp (GstTheoraEnc * theoraenc, ogg_int64_t granulepos)
+{
+  guint64 iframe, pframe;
+  int shift = theoraenc->info.keyframe_granule_shift;
+
+  if (granulepos < 0)
+    return GST_CLOCK_TIME_NONE;
+
+  iframe = granulepos >> shift;
+  pframe = granulepos - (iframe << shift);
+
+  /* num and den are 32 bit, so we can safely multiply with GST_SECOND */
+  return gst_util_uint64_scale ((guint64) (iframe + pframe),
+      GST_SECOND * theoraenc->info.fps_denominator,
+      theoraenc->info.fps_numerator);
+}
+
+/* Generate a dummy encoder context for use in th_encode_ctl queries
+   Release with th_encode_free()
+   This and the next routine from theora/examples/libtheora_info.c */
+static th_enc_ctx *
+dummy_encode_ctx (void)
+{
+  th_enc_ctx *ctx;
+  th_info info;
+
+  /* set the minimal video parameters */
+  th_info_init (&info);
+  info.frame_width = 320;
+  info.frame_height = 240;
+  info.fps_numerator = 1;
+  info.fps_denominator = 1;
+
+  /* allocate and initialize a context object */
+  ctx = th_encode_alloc (&info);
+  if (!ctx)
+    GST_WARNING ("Failed to allocate dummy encoder context.");
+
+  /* clear the info struct */
+  th_info_clear (&info);
+
+  return ctx;
+}
+
+/* Query the current and maximum values for the 'speed level' setting.
+   This can be used to ask the encoder to trade off encoding quality
+   vs. performance cost, for example to adapt to realtime constraints. */
+static int
+check_speed_level (th_enc_ctx * ctx, int *current, int *max)
+{
+  int ret;
+
+  /* query the current speed level */
+  ret = th_encode_ctl (ctx, TH_ENCCTL_GET_SPLEVEL, current, sizeof (int));
+  if (ret) {
+    GST_WARNING ("Error %d getting current speed level.", ret);
+    return ret;
+  }
+  /* query the maximum speed level, which varies by encoder version */
+  ret = th_encode_ctl (ctx, TH_ENCCTL_GET_SPLEVEL_MAX, max, sizeof (int));
+  if (ret) {
+    GST_WARNING ("Error %d getting maximum speed level.", ret);
+    return ret;
+  }
+
+  return 0;
+}
+
+static GstStaticPadTemplate theora_enc_sink_factory =
+GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-raw-yuv, "
+        "format = (fourcc) { I420, Y42B, Y444 }, "
+        "framerate = (fraction) [1/MAX, MAX], "
+        "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
+    );
+
+static GstStaticPadTemplate theora_enc_src_factory =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-theora")
+    );
+
+static void
+_do_init (GType object_type)
+{
+  const GInterfaceInfo preset_interface_info = {
+    NULL,                       /* interface_init */
+    NULL,                       /* interface_finalize */
+    NULL                        /* interface_data */
+  };
+
+  g_type_add_interface_static (object_type, GST_TYPE_PRESET,
+      &preset_interface_info);
+}
+
+GST_BOILERPLATE_FULL (GstTheoraEnc, gst_theora_enc, GstElement,
+    GST_TYPE_ELEMENT, _do_init);
+
+static gboolean theora_enc_sink_event (GstPad * pad, GstEvent * event);
+static gboolean theora_enc_src_event (GstPad * pad, GstEvent * event);
+static GstFlowReturn theora_enc_chain (GstPad * pad, GstBuffer * buffer);
+static GstStateChangeReturn theora_enc_change_state (GstElement * element,
+    GstStateChange transition);
+static GstCaps *theora_enc_sink_getcaps (GstPad * pad);
+static gboolean theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps);
+static void theora_enc_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+static void theora_enc_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void theora_enc_finalize (GObject * object);
+
+static gboolean theora_enc_write_multipass_cache (GstTheoraEnc * enc,
+    gboolean begin, gboolean eos);
+
+static void
+gst_theora_enc_base_init (gpointer g_class)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&theora_enc_src_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&theora_enc_sink_factory));
+  gst_element_class_set_details_simple (element_class,
+      "Theora video encoder", "Codec/Encoder/Video",
+      "encode raw YUV video to a theora stream",
+      "Wim Taymans <wim@fluendo.com>");
+}
+
+static void
+gst_theora_enc_class_init (GstTheoraEncClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  /* query runtime encoder properties */
+  th_enc_ctx *th_ctx;
+  int default_speed_level = THEORA_DEF_SPEEDLEVEL;
+  int max_speed_level = default_speed_level;
+
+  GST_DEBUG_CATEGORY_INIT (theoraenc_debug, "theoraenc", 0, "Theora encoder");
+
+  th_ctx = dummy_encode_ctx ();
+  if (th_ctx) {
+    if (!check_speed_level (th_ctx, &default_speed_level, &max_speed_level))
+      GST_WARNING
+          ("Failed to determine settings for the speed-level property.");
+    th_encode_free (th_ctx);
+  }
+
+  gobject_class->set_property = theora_enc_set_property;
+  gobject_class->get_property = theora_enc_get_property;
+  gobject_class->finalize = theora_enc_finalize;
+
+  g_object_class_install_property (gobject_class, PROP_CENTER,
+      g_param_spec_boolean ("center", "Center",
+          "ignored and kept for API compat only", TRUE,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_BORDER,
+      g_param_spec_enum ("border", "Border",
+          "ignored and kept for API compat only",
+          GST_TYPE_BORDER_MODE, BORDER_BLACK,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  /* general encoding stream options */
+  g_object_class_install_property (gobject_class, PROP_BITRATE,
+      g_param_spec_int ("bitrate", "Bitrate", "Compressed video bitrate (kbps)",
+          0, (1 << 24) - 1, THEORA_DEF_BITRATE,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+          GST_PARAM_MUTABLE_PLAYING));
+  g_object_class_install_property (gobject_class, PROP_QUALITY,
+      g_param_spec_int ("quality", "Quality", "Video quality", 0, 63,
+          THEORA_DEF_QUALITY,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+          GST_PARAM_MUTABLE_PLAYING));
+  g_object_class_install_property (gobject_class, PROP_QUICK,
+      g_param_spec_boolean ("quick", "Quick",
+          "ignored and kept for API compat only", TRUE,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_KEYFRAME_AUTO,
+      g_param_spec_boolean ("keyframe-auto", "Keyframe Auto",
+          "Automatic keyframe detection", THEORA_DEF_KEYFRAME_AUTO,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_KEYFRAME_FREQ,
+      g_param_spec_int ("keyframe-freq", "Keyframe frequency",
+          "Keyframe frequency", 1, 32768, THEORA_DEF_KEYFRAME_FREQ,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_KEYFRAME_FREQ_FORCE,
+      g_param_spec_int ("keyframe-force", "Keyframe force",
+          "Force keyframe every N frames", 1, 32768,
+          THEORA_DEF_KEYFRAME_FREQ_FORCE,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_KEYFRAME_THRESHOLD,
+      g_param_spec_int ("keyframe-threshold", "Keyframe threshold",
+          "ignored and kept for API compat only", 0, 32768, 80,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_KEYFRAME_MINDISTANCE,
+      g_param_spec_int ("keyframe-mindistance", "Keyframe mindistance",
+          "ignored and kept for API compat only", 1, 32768, 8,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_NOISE_SENSITIVITY,
+      g_param_spec_int ("noise-sensitivity", "Noise sensitivity",
+          "ignored and kept for API compat only", 0, 32768, 1,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_SHARPNESS,
+      g_param_spec_int ("sharpness", "Sharpness",
+          "ignored and kept for API compat only", 0, 2, 0,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_SPEEDLEVEL,
+      g_param_spec_int ("speed-level", "Speed level",
+          "Controls the amount of analysis performed when encoding."
+          " Higher values trade compression quality for speed."
+          " This property requires libtheora version >= 1.0"
+          ", and the maximum value may vary based on encoder version.",
+          0, max_speed_level, default_speed_level,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+          G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_VP3_COMPATIBLE,
+      g_param_spec_boolean ("vp3-compatible", "VP3 Compatible",
+          "Disables non-VP3 compatible features",
+          THEORA_DEF_VP3_COMPATIBLE,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_DROP_FRAMES,
+      g_param_spec_boolean ("drop-frames", "VP3 Compatible",
+          "Allow or disallow frame dropping",
+          THEORA_DEF_DROP_FRAMES,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_CAP_OVERFLOW,
+      g_param_spec_boolean ("cap-overflow", "VP3 Compatible",
+          "Enable capping of bit reservoir overflows",
+          THEORA_DEF_CAP_OVERFLOW,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_CAP_UNDERFLOW,
+      g_param_spec_boolean ("cap-underflow", "VP3 Compatible",
+          "Enable capping of bit reservoir underflows",
+          THEORA_DEF_CAP_UNDERFLOW,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_RATE_BUFFER,
+      g_param_spec_int ("rate-buffer", "Rate Control Buffer",
+          "Sets the size of the rate control buffer, in units of frames.  "
+          "The default value of 0 instructs the encoder to automatically "
+          "select an appropriate value",
+          0, 1000, THEORA_DEF_RATE_BUFFER,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_MULTIPASS_CACHE_FILE,
+      g_param_spec_string ("multipass-cache-file", "Multipass Cache File",
+          "Multipass cache file", THEORA_DEF_MULTIPASS_CACHE_FILE,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_MULTIPASS_MODE,
+      g_param_spec_enum ("multipass-mode", "Multipass mode",
+          "Single pass or first/second pass", GST_TYPE_MULTIPASS_MODE,
+          THEORA_DEF_MULTIPASS_MODE,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gstelement_class->change_state = theora_enc_change_state;
+}
+
+static void
+gst_theora_enc_init (GstTheoraEnc * enc, GstTheoraEncClass * g_class)
+{
+  enc->sinkpad =
+      gst_pad_new_from_static_template (&theora_enc_sink_factory, "sink");
+  gst_pad_set_chain_function (enc->sinkpad, theora_enc_chain);
+  gst_pad_set_event_function (enc->sinkpad, theora_enc_sink_event);
+  gst_pad_set_getcaps_function (enc->sinkpad, theora_enc_sink_getcaps);
+  gst_pad_set_setcaps_function (enc->sinkpad, theora_enc_sink_setcaps);
+  gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad);
+
+  enc->srcpad =
+      gst_pad_new_from_static_template (&theora_enc_src_factory, "src");
+  gst_pad_set_event_function (enc->srcpad, theora_enc_src_event);
+  gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad);
+
+  gst_segment_init (&enc->segment, GST_FORMAT_UNDEFINED);
+
+  enc->video_bitrate = THEORA_DEF_BITRATE;
+  enc->video_quality = THEORA_DEF_QUALITY;
+  enc->keyframe_auto = THEORA_DEF_KEYFRAME_AUTO;
+  enc->keyframe_freq = THEORA_DEF_KEYFRAME_FREQ;
+  enc->keyframe_force = THEORA_DEF_KEYFRAME_FREQ_FORCE;
+
+  enc->expected_ts = GST_CLOCK_TIME_NONE;
+
+  /* enc->speed_level is set to the libtheora default by the constructor */
+  enc->vp3_compatible = THEORA_DEF_VP3_COMPATIBLE;
+  enc->drop_frames = THEORA_DEF_DROP_FRAMES;
+  enc->cap_overflow = THEORA_DEF_CAP_OVERFLOW;
+  enc->cap_underflow = THEORA_DEF_CAP_UNDERFLOW;
+  enc->rate_buffer = THEORA_DEF_RATE_BUFFER;
+
+  enc->multipass_mode = THEORA_DEF_MULTIPASS_MODE;
+  enc->multipass_cache_file = THEORA_DEF_MULTIPASS_CACHE_FILE;
+}
+
+static void
+theora_enc_clear_multipass_cache (GstTheoraEnc * enc)
+{
+  if (enc->multipass_cache_fd) {
+    g_io_channel_shutdown (enc->multipass_cache_fd, TRUE, NULL);
+    g_io_channel_unref (enc->multipass_cache_fd);
+    enc->multipass_cache_fd = NULL;
+  }
+
+  if (enc->multipass_cache_adapter) {
+    gst_object_unref (enc->multipass_cache_adapter);
+    enc->multipass_cache_adapter = NULL;
+  }
+}
+
+static void
+theora_enc_finalize (GObject * object)
+{
+  GstTheoraEnc *enc = GST_THEORA_ENC (object);
+
+  GST_DEBUG_OBJECT (enc, "Finalizing");
+  if (enc->encoder)
+    th_encode_free (enc->encoder);
+  th_comment_clear (&enc->comment);
+  th_info_clear (&enc->info);
+  g_free (enc->multipass_cache_file);
+
+  theora_enc_clear_multipass_cache (enc);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+theora_enc_reset (GstTheoraEnc * enc)
+{
+  ogg_uint32_t keyframe_force;
+  int rate_flags;
+
+  GST_OBJECT_LOCK (enc);
+  enc->info.target_bitrate = enc->video_bitrate;
+  enc->info.quality = enc->video_quality;
+  enc->bitrate_changed = FALSE;
+  enc->quality_changed = FALSE;
+  GST_OBJECT_UNLOCK (enc);
+
+  if (enc->encoder)
+    th_encode_free (enc->encoder);
+  enc->encoder = th_encode_alloc (&enc->info);
+  /* We ensure this function cannot fail. */
+  g_assert (enc->encoder != NULL);
+  th_encode_ctl (enc->encoder, TH_ENCCTL_SET_SPLEVEL, &enc->speed_level,
+      sizeof (enc->speed_level));
+  th_encode_ctl (enc->encoder, TH_ENCCTL_SET_VP3_COMPATIBLE,
+      &enc->vp3_compatible, sizeof (enc->vp3_compatible));
+
+  rate_flags = 0;
+  if (enc->drop_frames)
+    rate_flags |= TH_RATECTL_DROP_FRAMES;
+  if (enc->drop_frames)
+    rate_flags |= TH_RATECTL_CAP_OVERFLOW;
+  if (enc->drop_frames)
+    rate_flags |= TH_RATECTL_CAP_UNDERFLOW;
+  th_encode_ctl (enc->encoder, TH_ENCCTL_SET_RATE_FLAGS,
+      &rate_flags, sizeof (rate_flags));
+
+  if (enc->rate_buffer) {
+    th_encode_ctl (enc->encoder, TH_ENCCTL_SET_RATE_BUFFER,
+        &enc->rate_buffer, sizeof (enc->rate_buffer));
+  } else {
+    /* FIXME */
+  }
+
+  keyframe_force = enc->keyframe_auto ?
+      enc->keyframe_force : enc->keyframe_freq;
+  th_encode_ctl (enc->encoder, TH_ENCCTL_SET_KEYFRAME_FREQUENCY_FORCE,
+      &keyframe_force, sizeof (keyframe_force));
+
+  /* Get placeholder data */
+  if (enc->multipass_cache_fd
+      && enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS)
+    theora_enc_write_multipass_cache (enc, TRUE, FALSE);
+}
+
+static void
+theora_enc_clear (GstTheoraEnc * enc)
+{
+  enc->packetno = 0;
+  enc->bytes_out = 0;
+  enc->granulepos_offset = 0;
+  enc->timestamp_offset = 0;
+
+  enc->next_ts = GST_CLOCK_TIME_NONE;
+  enc->next_discont = FALSE;
+  enc->expected_ts = GST_CLOCK_TIME_NONE;
+}
+
+static char *
+theora_enc_get_supported_formats (void)
+{
+  th_enc_ctx *encoder;
+  th_info info;
+  struct
+  {
+    th_pixel_fmt pixelformat;
+    const char *fourcc;
+  } formats[] = {
+    {
+    TH_PF_420, "I420"}, {
+    TH_PF_422, "Y42B"}, {
+    TH_PF_444, "Y444"}
+  };
+  GString *string = NULL;
+  guint i;
+
+  th_info_init (&info);
+  info.frame_width = 16;
+  info.frame_height = 16;
+  info.fps_numerator = 25;
+  info.fps_denominator = 1;
+  for (i = 0; i < G_N_ELEMENTS (formats); i++) {
+    info.pixel_fmt = formats[i].pixelformat;
+
+    encoder = th_encode_alloc (&info);
+    if (encoder == NULL)
+      continue;
+
+    GST_LOG ("format %s is supported", formats[i].fourcc);
+    th_encode_free (encoder);
+
+    if (string == NULL) {
+      string = g_string_new (formats[i].fourcc);
+    } else {
+      g_string_append (string, ", ");
+      g_string_append (string, formats[i].fourcc);
+    }
+  }
+  th_info_clear (&info);
+
+  return string == NULL ? NULL : g_string_free (string, FALSE);
+}
+
+static GstCaps *
+theora_enc_sink_getcaps (GstPad * pad)
+{
+  GstCaps *caps;
+  char *supported_formats, *caps_string;
+
+  supported_formats = theora_enc_get_supported_formats ();
+  if (!supported_formats) {
+    GST_WARNING ("no supported formats found. Encoder disabled?");
+    return gst_caps_new_empty ();
+  }
+
+  caps_string = g_strdup_printf ("video/x-raw-yuv, "
+      "format = (fourcc) { %s }, "
+      "framerate = (fraction) [1/MAX, MAX], "
+      "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]",
+      supported_formats);
+  caps = gst_caps_from_string (caps_string);
+  g_free (caps_string);
+  g_free (supported_formats);
+  GST_DEBUG ("Supported caps: %" GST_PTR_FORMAT, caps);
+
+  return caps;
+}
+
+static gboolean
+theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+  GstStructure *structure = gst_caps_get_structure (caps, 0);
+  GstTheoraEnc *enc = GST_THEORA_ENC (gst_pad_get_parent (pad));
+  guint32 fourcc;
+  const GValue *par;
+  gint fps_n, fps_d;
+
+  gst_structure_get_fourcc (structure, "format", &fourcc);
+  gst_structure_get_int (structure, "width", &enc->width);
+  gst_structure_get_int (structure, "height", &enc->height);
+  gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d);
+  par = gst_structure_get_value (structure, "pixel-aspect-ratio");
+
+  th_info_clear (&enc->info);
+  th_info_init (&enc->info);
+  /* Theora has a divisible-by-sixteen restriction for the encoded video size but
+   * we can define a picture area using pic_width/pic_height */
+  enc->info.frame_width = GST_ROUND_UP_16 (enc->width);
+  enc->info.frame_height = GST_ROUND_UP_16 (enc->height);
+  enc->info.pic_width = enc->width;
+  enc->info.pic_height = enc->height;
+  switch (fourcc) {
+    case GST_MAKE_FOURCC ('I', '4', '2', '0'):
+      enc->info.pixel_fmt = TH_PF_420;
+      break;
+    case GST_MAKE_FOURCC ('Y', '4', '2', 'B'):
+      enc->info.pixel_fmt = TH_PF_422;
+      break;
+    case GST_MAKE_FOURCC ('Y', '4', '4', '4'):
+      enc->info.pixel_fmt = TH_PF_444;
+      break;
+    default:
+      g_assert_not_reached ();
+  }
+
+  enc->info.fps_numerator = enc->fps_n = fps_n;
+  enc->info.fps_denominator = enc->fps_d = fps_d;
+  if (par) {
+    enc->info.aspect_numerator = gst_value_get_fraction_numerator (par);
+    enc->info.aspect_denominator = gst_value_get_fraction_denominator (par);
+  } else {
+    /* setting them to 0 indicates that the decoder can chose a good aspect
+     * ratio, defaulting to 1/1 */
+    enc->info.aspect_numerator = 0;
+    enc->info.aspect_denominator = 0;
+  }
+
+  enc->info.colorspace = TH_CS_UNSPECIFIED;
+
+  /* as done in theora */
+  enc->info.keyframe_granule_shift = _ilog (enc->keyframe_force - 1);
+  GST_DEBUG_OBJECT (enc,
+      "keyframe_frequency_force is %d, granule shift is %d",
+      enc->keyframe_force, enc->info.keyframe_granule_shift);
+
+  theora_enc_reset (enc);
+  enc->initialised = TRUE;
+
+  gst_object_unref (enc);
+
+  return TRUE;
+}
+
+static guint64
+granulepos_add (guint64 granulepos, guint64 addend, gint shift)
+{
+  guint64 iframe, pframe;
+
+  iframe = granulepos >> shift;
+  pframe = granulepos - (iframe << shift);
+  iframe += addend;
+
+  return (iframe << shift) + pframe;
+}
+
+/* prepare a buffer for transmission by passing data through libtheora */
+static GstFlowReturn
+theora_buffer_from_packet (GstTheoraEnc * enc, ogg_packet * packet,
+    GstClockTime timestamp, GstClockTime running_time,
+    GstClockTime duration, GstBuffer ** buffer)
+{
+  GstBuffer *buf;
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  buf = gst_buffer_new_and_alloc (packet->bytes);
+  if (!buf) {
+    GST_WARNING_OBJECT (enc, "Could not allocate buffer");
+    ret = GST_FLOW_ERROR;
+    goto done;
+  }
+
+  memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes);
+  gst_buffer_set_caps (buf, GST_PAD_CAPS (enc->srcpad));
+  /* see ext/ogg/README; OFFSET_END takes "our" granulepos, OFFSET its
+   * time representation */
+  GST_BUFFER_OFFSET_END (buf) =
+      granulepos_add (packet->granulepos, enc->granulepos_offset,
+      enc->info.keyframe_granule_shift);
+  GST_BUFFER_OFFSET (buf) = granulepos_to_timestamp (enc,
+      GST_BUFFER_OFFSET_END (buf));
+
+  GST_BUFFER_TIMESTAMP (buf) = timestamp;
+  GST_BUFFER_DURATION (buf) = duration;
+
+  if (enc->next_discont) {
+    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
+    enc->next_discont = FALSE;
+  }
+
+  /* the second most significant bit of the first data byte is cleared
+   * for keyframes */
+  if (packet->bytes > 0 && (packet->packet[0] & 0x40) == 0) {
+    GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
+  } else {
+    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
+  }
+  enc->packetno++;
+
+done:
+  *buffer = buf;
+  return ret;
+}
+
+/* push out the buffer and do internal bookkeeping */
+static GstFlowReturn
+theora_push_buffer (GstTheoraEnc * enc, GstBuffer * buffer)
+{
+  GstFlowReturn ret;
+
+  enc->bytes_out += GST_BUFFER_SIZE (buffer);
+
+  ret = gst_pad_push (enc->srcpad, buffer);
+
+  return ret;
+}
+
+static GstFlowReturn
+theora_push_packet (GstTheoraEnc * enc, ogg_packet * packet,
+    GstClockTime timestamp, GstClockTime running_time, GstClockTime duration)
+{
+  GstBuffer *buf;
+  GstFlowReturn ret;
+
+  ret =
+      theora_buffer_from_packet (enc, packet, timestamp, running_time, duration,
+      &buf);
+  if (ret == GST_FLOW_OK)
+    ret = theora_push_buffer (enc, buf);
+
+  return ret;
+}
+
+static GstCaps *
+theora_set_header_on_caps (GstCaps * caps, GSList * buffers)
+{
+  GstStructure *structure;
+  GValue array = { 0 };
+  GValue value = { 0 };
+  GstBuffer *buffer;
+  GSList *walk;
+
+  caps = gst_caps_make_writable (caps);
+  structure = gst_caps_get_structure (caps, 0);
+
+  /* put copies of the buffers in a fixed list */
+  g_value_init (&array, GST_TYPE_ARRAY);
+
+  for (walk = buffers; walk; walk = walk->next) {
+    buffer = walk->data;
+
+    /* mark buffer */
+    GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_IN_CAPS);
+
+    /* Copy buffer, because we can't use the original -
+     * it creates a circular refcount with the caps<->buffers */
+    buffer = gst_buffer_copy (buffer);
+
+    g_value_init (&value, GST_TYPE_BUFFER);
+    gst_value_set_buffer (&value, buffer);
+    gst_value_array_append_value (&array, &value);
+    g_value_unset (&value);
+
+    /* Unref our copy */
+    gst_buffer_unref (buffer);
+  }
+
+  gst_structure_set_value (structure, "streamheader", &array);
+  g_value_unset (&array);
+
+  return caps;
+}
+
+static void
+theora_enc_force_keyframe (GstTheoraEnc * enc)
+{
+  GstClockTime next_ts;
+
+  /* make sure timestamps increment after resetting the decoder */
+  next_ts = enc->next_ts + enc->timestamp_offset;
+
+  theora_enc_reset (enc);
+  enc->granulepos_offset =
+      gst_util_uint64_scale (next_ts, enc->fps_n, GST_SECOND * enc->fps_d);
+  enc->timestamp_offset = next_ts;
+  enc->next_ts = 0;
+}
+
+static gboolean
+theora_enc_sink_event (GstPad * pad, GstEvent * event)
+{
+  GstTheoraEnc *enc;
+  ogg_packet op;
+  gboolean res;
+
+  enc = GST_THEORA_ENC (GST_PAD_PARENT (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_NEWSEGMENT:
+    {
+      gboolean update;
+      gdouble rate, applied_rate;
+      GstFormat format;
+      gint64 start, stop, time;
+
+      gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
+          &format, &start, &stop, &time);
+
+      gst_segment_set_newsegment_full (&enc->segment, update, rate,
+          applied_rate, format, start, stop, time);
+
+      res = gst_pad_push_event (enc->srcpad, event);
+      break;
+    }
+    case GST_EVENT_EOS:
+      if (enc->initialised) {
+        /* push last packet with eos flag, should not be called */
+        while (th_encode_packetout (enc->encoder, 1, &op)) {
+          GstClockTime next_time =
+              th_granule_time (enc->encoder, op.granulepos) * GST_SECOND;
+
+          theora_push_packet (enc, &op, GST_CLOCK_TIME_NONE, enc->next_ts,
+              next_time - enc->next_ts);
+          enc->next_ts = next_time;
+        }
+      }
+      if (enc->initialised && enc->multipass_cache_fd
+          && enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS)
+        theora_enc_write_multipass_cache (enc, TRUE, TRUE);
+
+      theora_enc_clear_multipass_cache (enc);
+
+      res = gst_pad_push_event (enc->srcpad, event);
+      break;
+    case GST_EVENT_FLUSH_STOP:
+      gst_segment_init (&enc->segment, GST_FORMAT_UNDEFINED);
+      res = gst_pad_push_event (enc->srcpad, event);
+      break;
+    case GST_EVENT_CUSTOM_DOWNSTREAM:
+    {
+      const GstStructure *s;
+
+      s = gst_event_get_structure (event);
+
+      if (gst_structure_has_name (s, "GstForceKeyUnit"))
+        theora_enc_force_keyframe (enc);
+      res = gst_pad_push_event (enc->srcpad, event);
+      break;
+    }
+    default:
+      res = gst_pad_push_event (enc->srcpad, event);
+      break;
+  }
+  return res;
+}
+
+static gboolean
+theora_enc_src_event (GstPad * pad, GstEvent * event)
+{
+  GstTheoraEnc *enc;
+  gboolean res = TRUE;
+
+  enc = GST_THEORA_ENC (GST_PAD_PARENT (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_CUSTOM_UPSTREAM:
+    {
+      const GstStructure *s;
+
+      s = gst_event_get_structure (event);
+
+      if (gst_structure_has_name (s, "GstForceKeyUnit")) {
+        GST_OBJECT_LOCK (enc);
+        enc->force_keyframe = TRUE;
+        GST_OBJECT_UNLOCK (enc);
+        /* consume the event */
+        res = TRUE;
+        gst_event_unref (event);
+      } else {
+        res = gst_pad_push_event (enc->sinkpad, event);
+      }
+      break;
+    }
+    default:
+      res = gst_pad_push_event (enc->sinkpad, event);
+      break;
+  }
+
+  return res;
+}
+
+static gboolean
+theora_enc_is_discontinuous (GstTheoraEnc * enc, GstClockTime timestamp,
+    GstClockTime duration)
+{
+  GstClockTimeDiff max_diff;
+  gboolean ret = FALSE;
+
+  /* Allow 3/4 a frame off */
+  max_diff = (enc->info.fps_denominator * GST_SECOND * 3) /
+      (enc->info.fps_numerator * 4);
+
+  if (timestamp != GST_CLOCK_TIME_NONE
+      && enc->expected_ts != GST_CLOCK_TIME_NONE) {
+    if ((GstClockTimeDiff) (timestamp - enc->expected_ts) > max_diff) {
+      GST_DEBUG_OBJECT (enc, "Incoming TS %" GST_TIME_FORMAT
+          " exceeds expected value %" GST_TIME_FORMAT
+          " by too much, marking discontinuity",
+          GST_TIME_ARGS (timestamp), GST_TIME_ARGS (enc->expected_ts));
+      ret = TRUE;
+    }
+  }
+
+  if (GST_CLOCK_TIME_IS_VALID (duration))
+    enc->expected_ts = timestamp + duration;
+  else
+    enc->expected_ts = GST_CLOCK_TIME_NONE;
+
+  return ret;
+}
+
+static void
+theora_enc_init_buffer (th_ycbcr_buffer buf, th_info * info, guint8 * data)
+{
+  GstVideoFormat format;
+  guint i;
+
+  switch (info->pixel_fmt) {
+    case TH_PF_444:
+      format = GST_VIDEO_FORMAT_Y444;
+      break;
+    case TH_PF_420:
+      format = GST_VIDEO_FORMAT_I420;
+      break;
+    case TH_PF_422:
+      format = GST_VIDEO_FORMAT_Y42B;
+      break;
+    default:
+      g_assert_not_reached ();
+  }
+
+  /* According to Theora developer Timothy Terriberry, the Theora 
+   * encoder will not use memory outside of pic_width/height, even when
+   * the frame size is bigger. The values outside this region will be encoded
+   * to default values.
+   * Due to this, setting the frame's width/height as the buffer width/height
+   * is perfectly ok, even though it does not strictly look ok.
+   */
+  for (i = 0; i < 3; i++) {
+    buf[i].width =
+        gst_video_format_get_component_width (format, i, info->frame_width);
+    buf[i].height =
+        gst_video_format_get_component_height (format, i, info->frame_height);
+
+    buf[i].data =
+        data + gst_video_format_get_component_offset (format, i,
+        info->pic_width, info->pic_height);
+    buf[i].stride =
+        gst_video_format_get_row_stride (format, i, info->pic_width);
+  }
+}
+
+static gboolean
+theora_enc_read_multipass_cache (GstTheoraEnc * enc)
+{
+  GstBuffer *cache_buf;
+  const guint8 *cache_data;
+  gsize bytes_read = 0, bytes_consumed = 0;
+  GIOStatus stat = G_IO_STATUS_NORMAL;
+  gboolean done = FALSE;
+
+  while (!done) {
+    if (gst_adapter_available (enc->multipass_cache_adapter) == 0) {
+      cache_buf = gst_buffer_new_and_alloc (512);
+      stat = g_io_channel_read_chars (enc->multipass_cache_fd,
+          (gchar *) GST_BUFFER_DATA (cache_buf), GST_BUFFER_SIZE (cache_buf),
+          &bytes_read, NULL);
+
+      if (bytes_read <= 0) {
+        gst_buffer_unref (cache_buf);
+        break;
+      } else {
+        GST_BUFFER_SIZE (cache_buf) = bytes_read;
+
+        gst_adapter_push (enc->multipass_cache_adapter, cache_buf);
+      }
+    }
+    if (gst_adapter_available (enc->multipass_cache_adapter) == 0)
+      break;
+
+    bytes_read =
+        MIN (gst_adapter_available (enc->multipass_cache_adapter), 512);
+
+    cache_data = gst_adapter_peek (enc->multipass_cache_adapter, bytes_read);
+
+    bytes_consumed =
+        th_encode_ctl (enc->encoder, TH_ENCCTL_2PASS_IN, (guint8 *) cache_data,
+        bytes_read);
+    done = bytes_consumed <= 0;
+    if (bytes_consumed > 0)
+      gst_adapter_flush (enc->multipass_cache_adapter, bytes_consumed);
+  }
+
+  if (stat == G_IO_STATUS_ERROR || (stat == G_IO_STATUS_EOF && bytes_read == 0)
+      || bytes_consumed < 0) {
+    GST_ELEMENT_ERROR (enc, RESOURCE, READ, (NULL),
+        ("Failed to read multipass cache file"));
+    return FALSE;
+  }
+  return TRUE;
+}
+
+static gboolean
+theora_enc_write_multipass_cache (GstTheoraEnc * enc, gboolean begin,
+    gboolean eos)
+{
+  GError *err = NULL;
+  GIOStatus stat = G_IO_STATUS_NORMAL;
+  gint bytes_read = 0;
+  gsize bytes_written = 0;
+  gchar *buf;
+
+  if (begin)
+    stat = g_io_channel_seek_position (enc->multipass_cache_fd, 0, G_SEEK_SET,
+        &err);
+  if (stat != G_IO_STATUS_ERROR) {
+    do {
+      bytes_read =
+          th_encode_ctl (enc->encoder, TH_ENCCTL_2PASS_OUT, &buf, sizeof (buf));
+      if (bytes_read > 0)
+        g_io_channel_write_chars (enc->multipass_cache_fd, buf, bytes_read,
+            &bytes_written, NULL);
+    } while (bytes_read > 0 && bytes_written > 0);
+
+  }
+
+  if (stat == G_IO_STATUS_ERROR || bytes_read < 0 || bytes_written < 0) {
+    if (begin) {
+      if (eos)
+        GST_ELEMENT_WARNING (enc, RESOURCE, WRITE, (NULL),
+            ("Failed to seek to beginning of multipass cache file: %s",
+                err->message));
+      else
+        GST_ELEMENT_ERROR (enc, RESOURCE, WRITE, (NULL),
+            ("Failed to seek to beginning of multipass cache file: %s",
+                err->message));
+    } else {
+      GST_ELEMENT_ERROR (enc, RESOURCE, WRITE, (NULL),
+          ("Failed to write multipass cache file"));
+    }
+    if (err)
+      g_error_free (err);
+
+    return FALSE;
+  }
+  return TRUE;
+}
+
+static GstFlowReturn
+theora_enc_chain (GstPad * pad, GstBuffer * buffer)
+{
+  GstTheoraEnc *enc;
+  ogg_packet op;
+  GstClockTime timestamp, duration, running_time;
+  GstFlowReturn ret;
+  gboolean force_keyframe;
+
+  enc = GST_THEORA_ENC (GST_PAD_PARENT (pad));
+
+  /* we keep track of two timelines.
+   * - The timestamps from the incomming buffers, which we copy to the outgoing
+   *   encoded buffers as-is. We need to do this as we simply forward the
+   *   newsegment events.
+   * - The running_time of the buffers, which we use to construct the granulepos
+   *   in the packets.
+   */
+  timestamp = GST_BUFFER_TIMESTAMP (buffer);
+  duration = GST_BUFFER_DURATION (buffer);
+
+  running_time =
+      gst_segment_to_running_time (&enc->segment, GST_FORMAT_TIME, timestamp);
+  if ((gint64) running_time < 0) {
+    GST_DEBUG_OBJECT (enc, "Dropping buffer, timestamp: %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
+    gst_buffer_unref (buffer);
+    return GST_FLOW_OK;
+  }
+
+  GST_OBJECT_LOCK (enc);
+  if (enc->bitrate_changed) {
+    long int bitrate = enc->video_bitrate;
+
+    th_encode_ctl (enc->encoder, TH_ENCCTL_SET_BITRATE, &bitrate,
+        sizeof (long int));
+    enc->bitrate_changed = FALSE;
+  }
+
+  if (enc->quality_changed) {
+    long int quality = enc->video_quality;
+
+    th_encode_ctl (enc->encoder, TH_ENCCTL_SET_QUALITY, &quality,
+        sizeof (long int));
+    enc->quality_changed = FALSE;
+  }
+
+  /* see if we need to schedule a keyframe */
+  force_keyframe = enc->force_keyframe;
+  enc->force_keyframe = FALSE;
+  GST_OBJECT_UNLOCK (enc);
+
+  if (force_keyframe) {
+    GstClockTime stream_time;
+    GstStructure *s;
+
+    stream_time = gst_segment_to_stream_time (&enc->segment,
+        GST_FORMAT_TIME, timestamp);
+
+    s = gst_structure_new ("GstForceKeyUnit",
+        "timestamp", G_TYPE_UINT64, timestamp,
+        "stream-time", G_TYPE_UINT64, stream_time,
+        "running-time", G_TYPE_UINT64, running_time, NULL);
+
+    theora_enc_force_keyframe (enc);
+
+    gst_pad_push_event (enc->srcpad,
+        gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s));
+  }
+
+  /* make sure we copy the discont flag to the next outgoing buffer when it's
+   * set on the incomming buffer */
+  if (GST_BUFFER_IS_DISCONT (buffer)) {
+    enc->next_discont = TRUE;
+  }
+
+  if (enc->packetno == 0) {
+    /* no packets written yet, setup headers */
+    GstCaps *caps;
+    GstBuffer *buf;
+    GSList *buffers = NULL;
+    int result;
+
+    enc->granulepos_offset = 0;
+    enc->timestamp_offset = 0;
+
+    GST_DEBUG_OBJECT (enc, "output headers");
+    /* Theora streams begin with three headers; the initial header (with
+       most of the codec setup parameters) which is mandated by the Ogg
+       bitstream spec.  The second header holds any comment fields.  The
+       third header holds the bitstream codebook.  We merely need to
+       make the headers, then pass them to libtheora one at a time;
+       libtheora handles the additional Ogg bitstream constraints */
+
+    /* create the remaining theora headers */
+    th_comment_clear (&enc->comment);
+    th_comment_init (&enc->comment);
+
+    while ((result =
+            th_encode_flushheader (enc->encoder, &enc->comment, &op)) > 0) {
+      ret =
+          theora_buffer_from_packet (enc, &op, GST_CLOCK_TIME_NONE,
+          GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, &buf);
+      if (ret != GST_FLOW_OK) {
+        goto header_buffer_alloc;
+      }
+      buffers = g_slist_prepend (buffers, buf);
+    }
+    if (result < 0) {
+      g_slist_foreach (buffers, (GFunc) gst_buffer_unref, NULL);
+      g_slist_free (buffers);
+      goto encoder_disabled;
+    }
+
+    buffers = g_slist_reverse (buffers);
+
+    /* mark buffers and put on caps */
+    caps = gst_pad_get_caps (enc->srcpad);
+    caps = theora_set_header_on_caps (caps, buffers);
+    GST_DEBUG ("here are the caps: %" GST_PTR_FORMAT, caps);
+    gst_pad_set_caps (enc->srcpad, caps);
+
+    g_slist_foreach (buffers, (GFunc) gst_buffer_set_caps, caps);
+
+    gst_caps_unref (caps);
+
+    /* push out the header buffers */
+    while (buffers) {
+      buf = buffers->data;
+      buffers = g_slist_delete_link (buffers, buffers);
+      if ((ret = theora_push_buffer (enc, buf)) != GST_FLOW_OK) {
+        g_slist_foreach (buffers, (GFunc) gst_buffer_unref, NULL);
+        g_slist_free (buffers);
+        goto header_push;
+      }
+    }
+
+    enc->granulepos_offset =
+        gst_util_uint64_scale (running_time, enc->fps_n,
+        GST_SECOND * enc->fps_d);
+    enc->timestamp_offset = running_time;
+    enc->next_ts = 0;
+  }
+
+  {
+    th_ycbcr_buffer ycbcr;
+    gint res;
+
+    theora_enc_init_buffer (ycbcr, &enc->info, GST_BUFFER_DATA (buffer));
+
+    if (theora_enc_is_discontinuous (enc, running_time, duration)) {
+      theora_enc_reset (enc);
+      enc->granulepos_offset =
+          gst_util_uint64_scale (running_time, enc->fps_n,
+          GST_SECOND * enc->fps_d);
+      enc->timestamp_offset = running_time;
+      enc->next_ts = 0;
+      enc->next_discont = TRUE;
+    }
+
+    if (enc->multipass_cache_fd
+        && enc->multipass_mode == MULTIPASS_MODE_SECOND_PASS) {
+      if (!theora_enc_read_multipass_cache (enc)) {
+        ret = GST_FLOW_ERROR;
+        goto multipass_read_failed;
+      }
+    }
+
+    res = th_encode_ycbcr_in (enc->encoder, ycbcr);
+    /* none of the failure cases can happen here */
+    g_assert (res == 0);
+
+    if (enc->multipass_cache_fd
+        && enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS) {
+      if (!theora_enc_write_multipass_cache (enc, FALSE, FALSE)) {
+        ret = GST_FLOW_ERROR;
+        goto multipass_write_failed;
+      }
+    }
+
+    ret = GST_FLOW_OK;
+    while (th_encode_packetout (enc->encoder, 0, &op)) {
+      GstClockTime next_time;
+
+      next_time = th_granule_time (enc->encoder, op.granulepos) * GST_SECOND;
+
+      ret =
+          theora_push_packet (enc, &op, timestamp, enc->next_ts,
+          next_time - enc->next_ts);
+
+      enc->next_ts = next_time;
+      if (ret != GST_FLOW_OK)
+        goto data_push;
+    }
+    gst_buffer_unref (buffer);
+  }
+
+  return ret;
+
+  /* ERRORS */
+multipass_read_failed:
+  {
+    gst_buffer_unref (buffer);
+    return ret;
+  }
+multipass_write_failed:
+  {
+    gst_buffer_unref (buffer);
+    return ret;
+  }
+header_buffer_alloc:
+  {
+    gst_buffer_unref (buffer);
+    return ret;
+  }
+header_push:
+  {
+    gst_buffer_unref (buffer);
+    return ret;
+  }
+data_push:
+  {
+    gst_buffer_unref (buffer);
+    return ret;
+  }
+encoder_disabled:
+  {
+    GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL),
+        ("libtheora has been compiled with the encoder disabled"));
+    gst_buffer_unref (buffer);
+    return GST_FLOW_ERROR;
+  }
+}
+
+static GstStateChangeReturn
+theora_enc_change_state (GstElement * element, GstStateChange transition)
+{
+  GstTheoraEnc *enc;
+  GstStateChangeReturn ret;
+
+  enc = GST_THEORA_ENC (element);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      GST_DEBUG_OBJECT (enc, "READY->PAUSED Initing theora state");
+      th_info_init (&enc->info);
+      th_comment_init (&enc->comment);
+      enc->packetno = 0;
+      enc->force_keyframe = FALSE;
+
+      if (enc->multipass_mode >= MULTIPASS_MODE_FIRST_PASS) {
+        GError *err = NULL;
+
+        if (!enc->multipass_cache_file) {
+          ret = GST_STATE_CHANGE_FAILURE;
+          GST_ELEMENT_ERROR (enc, LIBRARY, SETTINGS, (NULL), (NULL));
+          return ret;
+        }
+        enc->multipass_cache_fd =
+            g_io_channel_new_file (enc->multipass_cache_file,
+            (enc->multipass_mode == MULTIPASS_MODE_FIRST_PASS ? "w" : "r"),
+            &err);
+
+        if (enc->multipass_mode == MULTIPASS_MODE_SECOND_PASS)
+          enc->multipass_cache_adapter = gst_adapter_new ();
+
+        if (!enc->multipass_cache_fd) {
+          ret = GST_STATE_CHANGE_FAILURE;
+          GST_ELEMENT_ERROR (enc, RESOURCE, OPEN_READ, (NULL),
+              ("Failed to open multipass cache file: %s", err->message));
+          g_error_free (err);
+          return ret;
+        }
+
+        g_io_channel_set_encoding (enc->multipass_cache_fd, NULL, NULL);
+      }
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+      break;
+    default:
+      break;
+  }
+
+  ret = parent_class->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      GST_DEBUG_OBJECT (enc, "PAUSED->READY Clearing theora state");
+      if (enc->encoder) {
+        th_encode_free (enc->encoder);
+        enc->encoder = NULL;
+      }
+      th_comment_clear (&enc->comment);
+      th_info_clear (&enc->info);
+
+      theora_enc_clear (enc);
+      enc->initialised = FALSE;
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static void
+theora_enc_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstTheoraEnc *enc = GST_THEORA_ENC (object);
+
+  switch (prop_id) {
+    case PROP_CENTER:
+    case PROP_BORDER:
+    case PROP_QUICK:
+    case PROP_KEYFRAME_THRESHOLD:
+    case PROP_KEYFRAME_MINDISTANCE:
+    case PROP_NOISE_SENSITIVITY:
+    case PROP_SHARPNESS:
+      /* kept for API compat, but ignored */
+      break;
+    case PROP_BITRATE:
+      GST_OBJECT_LOCK (enc);
+      enc->video_bitrate = g_value_get_int (value) * 1000;
+      enc->bitrate_changed = TRUE;
+      GST_OBJECT_UNLOCK (enc);
+      break;
+    case PROP_QUALITY:
+      GST_OBJECT_LOCK (enc);
+      if (GST_STATE (enc) >= GST_STATE_PAUSED && enc->video_bitrate > 0) {
+        GST_WARNING_OBJECT (object, "Can't change from bitrate to quality mode"
+            " while playing");
+      } else {
+        enc->video_quality = g_value_get_int (value);
+        enc->video_bitrate = 0;
+        enc->quality_changed = TRUE;
+      }
+      GST_OBJECT_UNLOCK (enc);
+      break;
+    case PROP_KEYFRAME_AUTO:
+      enc->keyframe_auto = g_value_get_boolean (value);
+      break;
+    case PROP_KEYFRAME_FREQ:
+      enc->keyframe_freq = g_value_get_int (value);
+      break;
+    case PROP_KEYFRAME_FREQ_FORCE:
+      enc->keyframe_force = g_value_get_int (value);
+      break;
+    case PROP_SPEEDLEVEL:
+      enc->speed_level = g_value_get_int (value);
+      if (enc->encoder) {
+        th_encode_ctl (enc->encoder, TH_ENCCTL_SET_SPLEVEL, &enc->speed_level,
+            sizeof (enc->speed_level));
+      }
+      break;
+    case PROP_VP3_COMPATIBLE:
+      enc->vp3_compatible = g_value_get_boolean (value);
+      break;
+    case PROP_DROP_FRAMES:
+      enc->drop_frames = g_value_get_boolean (value);
+      break;
+    case PROP_CAP_OVERFLOW:
+      enc->cap_overflow = g_value_get_boolean (value);
+      break;
+    case PROP_CAP_UNDERFLOW:
+      enc->cap_underflow = g_value_get_boolean (value);
+      break;
+    case PROP_RATE_BUFFER:
+      enc->rate_buffer = g_value_get_int (value);
+      break;
+    case PROP_MULTIPASS_CACHE_FILE:
+      enc->multipass_cache_file = g_value_dup_string (value);
+      break;
+    case PROP_MULTIPASS_MODE:
+      enc->multipass_mode = g_value_get_enum (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+theora_enc_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstTheoraEnc *enc = GST_THEORA_ENC (object);
+
+  switch (prop_id) {
+    case PROP_CENTER:
+      g_value_set_boolean (value, TRUE);
+      break;
+    case PROP_BORDER:
+      g_value_set_enum (value, BORDER_BLACK);
+      break;
+    case PROP_BITRATE:
+      GST_OBJECT_LOCK (enc);
+      g_value_set_int (value, enc->video_bitrate / 1000);
+      GST_OBJECT_UNLOCK (enc);
+      break;
+    case PROP_QUALITY:
+      GST_OBJECT_LOCK (enc);
+      g_value_set_int (value, enc->video_quality);
+      GST_OBJECT_UNLOCK (enc);
+      break;
+    case PROP_QUICK:
+      g_value_set_boolean (value, TRUE);
+      break;
+    case PROP_KEYFRAME_AUTO:
+      g_value_set_boolean (value, enc->keyframe_auto);
+      break;
+    case PROP_KEYFRAME_FREQ:
+      g_value_set_int (value, enc->keyframe_freq);
+      break;
+    case PROP_KEYFRAME_FREQ_FORCE:
+      g_value_set_int (value, enc->keyframe_force);
+      break;
+    case PROP_KEYFRAME_THRESHOLD:
+      g_value_set_int (value, 80);
+      break;
+    case PROP_KEYFRAME_MINDISTANCE:
+      g_value_set_int (value, 8);
+      break;
+    case PROP_NOISE_SENSITIVITY:
+      g_value_set_int (value, 1);
+      break;
+    case PROP_SHARPNESS:
+      g_value_set_int (value, 0);
+      break;
+    case PROP_SPEEDLEVEL:
+      g_value_set_int (value, enc->speed_level);
+      break;
+    case PROP_VP3_COMPATIBLE:
+      g_value_set_boolean (value, enc->vp3_compatible);
+      break;
+    case PROP_DROP_FRAMES:
+      g_value_set_boolean (value, enc->drop_frames);
+      break;
+    case PROP_CAP_OVERFLOW:
+      g_value_set_boolean (value, enc->cap_overflow);
+      break;
+    case PROP_CAP_UNDERFLOW:
+      g_value_set_boolean (value, enc->cap_underflow);
+      break;
+    case PROP_RATE_BUFFER:
+      g_value_set_int (value, enc->rate_buffer);
+      break;
+    case PROP_MULTIPASS_CACHE_FILE:
+      g_value_set_string (value, enc->multipass_cache_file);
+      break;
+    case PROP_MULTIPASS_MODE:
+      g_value_set_enum (value, enc->multipass_mode);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}