/* Schrodinger
 * Copyright (C) 2006 David Schleef <ds@schleef.org>
 *
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/video/gstvideometa.h>
#include <gst/video/gstvideoencoder.h>
#include <gst/video/gstvideoutils.h>
#include <string.h>

#include <schroedinger/schro.h>
#include <schroedinger/schrobitstream.h>
#include <schroedinger/schrovirtframe.h>
#include <math.h>
#include "gstschroutils.h"

GST_DEBUG_CATEGORY_EXTERN (schro_debug);
#define GST_CAT_DEFAULT schro_debug

#define GST_TYPE_SCHRO_ENC \
  (gst_schro_enc_get_type())
#define GST_SCHRO_ENC(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SCHRO_ENC,GstSchroEnc))
#define GST_SCHRO_ENC_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SCHRO_ENC,GstSchroEncClass))
#define GST_IS_SCHRO_ENC(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SCHRO_ENC))
#define GST_IS_SCHRO_ENC_CLASS(obj) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SCHRO_ENC))

typedef struct _GstSchroEnc GstSchroEnc;
typedef struct _GstSchroEncClass GstSchroEncClass;

struct _GstSchroEnc
{
  GstVideoEncoder base_encoder;

  GstPad *sinkpad;
  GstPad *srcpad;

  /* state */
  SchroEncoder *encoder;
  SchroVideoFormat *video_format;

  guint64 last_granulepos;
  guint64 granule_offset;

  GstVideoCodecState *input_state;
};

struct _GstSchroEncClass
{
  GstVideoEncoderClass parent_class;
};

GType gst_schro_enc_get_type (void);



enum
{
  LAST_SIGNAL
};

enum
{
  ARG_0
};

static void gst_schro_enc_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_schro_enc_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

static GstFlowReturn gst_schro_enc_process (GstSchroEnc * schro_enc);

static gboolean gst_schro_enc_set_format (GstVideoEncoder *
    base_video_encoder, GstVideoCodecState * state);
static gboolean gst_schro_enc_start (GstVideoEncoder * base_video_encoder);
static gboolean gst_schro_enc_stop (GstVideoEncoder * base_video_encoder);
static GstFlowReturn gst_schro_enc_finish (GstVideoEncoder *
    base_video_encoder);
static GstFlowReturn gst_schro_enc_handle_frame (GstVideoEncoder *
    base_video_encoder, GstVideoCodecFrame * frame);
static GstFlowReturn gst_schro_enc_pre_push (GstVideoEncoder *
    base_video_encoder, GstVideoCodecFrame * frame);
static void gst_schro_enc_finalize (GObject * object);
static gboolean gst_schro_enc_propose_allocation (GstVideoEncoder * encoder,
    GstQuery * query);

static GstStaticPadTemplate gst_schro_enc_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_SCHRO_YUV_LIST))
    );

static GstStaticPadTemplate gst_schro_enc_src_template =
    GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-dirac;video/x-qt-part;video/x-mp4-part")
    );

#define parent_class gst_schro_enc_parent_class
G_DEFINE_TYPE (GstSchroEnc, gst_schro_enc, GST_TYPE_VIDEO_ENCODER);

static GType
register_enum_list (const SchroEncoderSetting * setting)
{
  GType type;
  static GEnumValue *enumtypes;
  int n;
  char *typename;
  int i;

  n = setting->max + 1;

  enumtypes = g_malloc0 ((n + 1) * sizeof (GEnumValue));
  for (i = 0; i < n; i++) {
    gchar *nick;

    enumtypes[i].value = i;
    nick = g_strdelimit (g_strdup (setting->enum_list[i]), "_", '-');
    enumtypes[i].value_name = g_intern_static_string (nick);
    enumtypes[i].value_nick = enumtypes[i].value_name;
  }

  typename = g_strdup_printf ("SchroEncoderSettingEnum_%s", setting->name);
  type = g_enum_register_static (typename, enumtypes);
  g_free (typename);

  return type;
}

static void
gst_schro_enc_class_init (GstSchroEncClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *element_class;
  GstVideoEncoderClass *basevideocoder_class;
  int i;

  gobject_class = G_OBJECT_CLASS (klass);
  element_class = GST_ELEMENT_CLASS (klass);
  basevideocoder_class = GST_VIDEO_ENCODER_CLASS (klass);

  gobject_class->set_property = gst_schro_enc_set_property;
  gobject_class->get_property = gst_schro_enc_get_property;
  gobject_class->finalize = gst_schro_enc_finalize;

  for (i = 0; i < schro_encoder_get_n_settings (); i++) {
    const SchroEncoderSetting *setting;

    setting = schro_encoder_get_setting_info (i);

    /* we do this by checking downstream caps, and the profile/level selected
     * should be read from the output caps and not from properties */
    if (strcmp (setting->name, "force_profile") == 0
        || strcmp (setting->name, "profile") == 0
        || strcmp (setting->name, "level") == 0)
      continue;

    /* we configure this based on the input caps */
    if (strcmp (setting->name, "interlaced_coding") == 0)
      continue;

    switch (setting->type) {
      case SCHRO_ENCODER_SETTING_TYPE_BOOLEAN:
        g_object_class_install_property (gobject_class, i + 1,
            g_param_spec_boolean (setting->name, setting->name, setting->name,
                setting->default_value, G_PARAM_READWRITE));
        break;
      case SCHRO_ENCODER_SETTING_TYPE_INT:
        g_object_class_install_property (gobject_class, i + 1,
            g_param_spec_int (setting->name, setting->name, setting->name,
                setting->min, setting->max, setting->default_value,
                G_PARAM_READWRITE));
        break;
      case SCHRO_ENCODER_SETTING_TYPE_ENUM:
        g_object_class_install_property (gobject_class, i + 1,
            g_param_spec_enum (setting->name, setting->name, setting->name,
                register_enum_list (setting), setting->default_value,
                G_PARAM_READWRITE));
        break;
      case SCHRO_ENCODER_SETTING_TYPE_DOUBLE:
        g_object_class_install_property (gobject_class, i + 1,
            g_param_spec_double (setting->name, setting->name, setting->name,
                setting->min, setting->max, setting->default_value,
                G_PARAM_READWRITE));
        break;
      default:
        break;
    }
  }

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_schro_enc_src_template));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_schro_enc_sink_template));

  gst_element_class_set_static_metadata (element_class, "Dirac Encoder",
      "Codec/Encoder/Video",
      "Encode raw video into Dirac stream", "David Schleef <ds@schleef.org>");

  basevideocoder_class->set_format =
      GST_DEBUG_FUNCPTR (gst_schro_enc_set_format);
  basevideocoder_class->start = GST_DEBUG_FUNCPTR (gst_schro_enc_start);
  basevideocoder_class->stop = GST_DEBUG_FUNCPTR (gst_schro_enc_stop);
  basevideocoder_class->finish = GST_DEBUG_FUNCPTR (gst_schro_enc_finish);
  basevideocoder_class->handle_frame =
      GST_DEBUG_FUNCPTR (gst_schro_enc_handle_frame);
  basevideocoder_class->pre_push = GST_DEBUG_FUNCPTR (gst_schro_enc_pre_push);
  basevideocoder_class->propose_allocation =
      GST_DEBUG_FUNCPTR (gst_schro_enc_propose_allocation);
}

static void
gst_schro_enc_init (GstSchroEnc * schro_enc)
{
  GST_DEBUG ("gst_schro_enc_init");

  /* Normally, we'd create the encoder in ->start(), but we use the
   * encoder to store object properties.  So it needs to be created
   * here. */
  schro_enc->encoder = schro_encoder_new ();
  schro_encoder_set_packet_assembly (schro_enc->encoder, TRUE);
  schro_enc->video_format = schro_encoder_get_video_format (schro_enc->encoder);
}

static void
gst_schro_enc_finalize (GObject * object)
{
  GstSchroEnc *schro_enc = GST_SCHRO_ENC (object);

  if (schro_enc->encoder) {
    schro_encoder_free (schro_enc->encoder);
    schro_enc->encoder = NULL;
  }
  if (schro_enc->video_format) {
    g_free (schro_enc->video_format);
    schro_enc->video_format = NULL;
  }
  if (schro_enc->input_state)
    gst_video_codec_state_unref (schro_enc->input_state);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static const gchar *
get_profile_name (int profile)
{
  switch (profile) {
    case 0:
      return "vc2-low-delay";
    case 1:
      return "vc2-simple";
    case 2:
      return "vc2-main";
    case 8:
      return "main";
    default:
      break;
  }
  return "unknown";
}

static const gchar *
get_level_name (int level)
{
  switch (level) {
    case 0:
      return "0";
    case 1:
      return "1";
    case 128:
      return "128";
    default:
      break;
  }
  /* need to add it to template caps, so return 0 for now */
  GST_WARNING ("unhandled dirac level %u", level);
  return "0";
}

static void
gst_schro_enc_negotiate_profile (GstSchroEnc * enc)
{
  GstStructure *s;
  const gchar *profile;
  const gchar *level;
  GstCaps *allowed_caps;

  allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (enc));

  GST_DEBUG_OBJECT (enc, "allowed caps: %" GST_PTR_FORMAT, allowed_caps);

  if (allowed_caps == NULL)
    return;

  if (gst_caps_is_empty (allowed_caps) || gst_caps_is_any (allowed_caps))
    goto out;

  allowed_caps = gst_caps_make_writable (allowed_caps);
  allowed_caps = gst_caps_fixate (allowed_caps);
  s = gst_caps_get_structure (allowed_caps, 0);

  profile = gst_structure_get_string (s, "profile");
  if (profile) {
    if (!strcmp (profile, "vc2-low-delay")) {
      schro_encoder_setting_set_double (enc->encoder, "force_profile", 1);
    } else if (!strcmp (profile, "vc2-simple")) {
      schro_encoder_setting_set_double (enc->encoder, "force_profile", 2);
    } else if (!strcmp (profile, "vc2-main")) {
      schro_encoder_setting_set_double (enc->encoder, "force_profile", 3);
    } else if (!strcmp (profile, "main")) {
      schro_encoder_setting_set_double (enc->encoder, "force_profile", 4);
    } else {
      GST_WARNING_OBJECT (enc, "ignoring unknown profile '%s'", profile);
    }
  }

  level = gst_structure_get_string (s, "level");
  if (level != NULL && strcmp (level, "0") != 0) {
    GST_FIXME_OBJECT (enc, "level setting not implemented");
  }

out:

  gst_caps_unref (allowed_caps);
}

static gboolean
gst_schro_enc_set_format (GstVideoEncoder * base_video_encoder,
    GstVideoCodecState * state)
{
  GstSchroEnc *schro_enc = GST_SCHRO_ENC (base_video_encoder);
  GstBuffer *seq_header_buffer;
  GstVideoInfo *info = &state->info;
  GstVideoCodecState *output_state;
  GstClockTime latency;
  GstCaps *out_caps;
  int level, profile;

  GST_DEBUG ("set_output_caps");

  schro_video_format_set_std_video_format (schro_enc->video_format,
      SCHRO_VIDEO_FORMAT_CUSTOM);

  switch (GST_VIDEO_INFO_FORMAT (info)) {
    case GST_VIDEO_FORMAT_I420:
    case GST_VIDEO_FORMAT_YV12:
#if SCHRO_CHECK_VERSION(1,0,11)
    case GST_VIDEO_FORMAT_Y42B:
#endif
      schro_enc->video_format->chroma_format = SCHRO_CHROMA_420;
      break;
    case GST_VIDEO_FORMAT_YUY2:
    case GST_VIDEO_FORMAT_UYVY:
#if SCHRO_CHECK_VERSION(1,0,11)
    case GST_VIDEO_FORMAT_v216:
    case GST_VIDEO_FORMAT_v210:
#endif
      schro_enc->video_format->chroma_format = SCHRO_CHROMA_422;
      break;
    case GST_VIDEO_FORMAT_AYUV:
#if SCHRO_CHECK_VERSION(1,0,12)
    case GST_VIDEO_FORMAT_ARGB:
#endif
#if SCHRO_CHECK_VERSION(1,0,11)
    case GST_VIDEO_FORMAT_Y444:
    case GST_VIDEO_FORMAT_AYUV64:
#endif
      schro_enc->video_format->chroma_format = SCHRO_CHROMA_444;
      break;
    default:
      g_assert_not_reached ();
  }

  schro_enc->video_format->frame_rate_numerator = GST_VIDEO_INFO_FPS_N (info);
  schro_enc->video_format->frame_rate_denominator = GST_VIDEO_INFO_FPS_D (info);

  /* Seems that schroenc doesn't like unknown framerates, so let's pick
   * the random value 30 FPS if the framerate is unknown.
   */
  if (schro_enc->video_format->frame_rate_denominator == 0 ||
      schro_enc->video_format->frame_rate_numerator == 0) {
    schro_enc->video_format->frame_rate_numerator = 30;
    schro_enc->video_format->frame_rate_denominator = 1;
  }

  schro_enc->video_format->width = GST_VIDEO_INFO_WIDTH (info);
  schro_enc->video_format->height = GST_VIDEO_INFO_HEIGHT (info);
  schro_enc->video_format->clean_width = GST_VIDEO_INFO_WIDTH (info);
  schro_enc->video_format->clean_height = GST_VIDEO_INFO_HEIGHT (info);
  schro_enc->video_format->left_offset = 0;
  schro_enc->video_format->top_offset = 0;

  schro_enc->video_format->aspect_ratio_numerator = GST_VIDEO_INFO_PAR_N (info);
  schro_enc->video_format->aspect_ratio_denominator =
      GST_VIDEO_INFO_PAR_D (info);

  switch (GST_VIDEO_INFO_FORMAT (&state->info)) {
    default:
      schro_video_format_set_std_signal_range (schro_enc->video_format,
          SCHRO_SIGNAL_RANGE_8BIT_VIDEO);
      break;
#if SCHRO_CHECK_VERSION(1,0,11)
    case GST_VIDEO_FORMAT_v210:
      schro_video_format_set_std_signal_range (schro_enc->video_format,
          SCHRO_SIGNAL_RANGE_10BIT_VIDEO);
      break;
    case GST_VIDEO_FORMAT_v216:
    case GST_VIDEO_FORMAT_AYUV64:
      schro_enc->video_format->luma_offset = 64 << 8;
      schro_enc->video_format->luma_excursion = 219 << 8;
      schro_enc->video_format->chroma_offset = 128 << 8;
      schro_enc->video_format->chroma_excursion = 224 << 8;
      break;
#endif
#if SCHRO_CHECK_VERSION(1,0,12)
    case GST_VIDEO_FORMAT_ARGB:
      schro_enc->video_format->luma_offset = 256;
      schro_enc->video_format->luma_excursion = 511;
      schro_enc->video_format->chroma_offset = 256;
      schro_enc->video_format->chroma_excursion = 511;
      break;
#endif
  }

  if (GST_VIDEO_INFO_IS_INTERLACED (&state->info)) {
    schro_enc->video_format->interlaced_coding = 1;
  }

  /* See if downstream caps specify profile/level */
  gst_schro_enc_negotiate_profile (schro_enc);

  /* Finally set latency */
  latency = gst_util_uint64_scale (GST_SECOND,
      schro_enc->video_format->frame_rate_denominator *
      (int) schro_encoder_setting_get_double (schro_enc->encoder,
          "queue_depth"), schro_enc->video_format->frame_rate_numerator);
  gst_video_encoder_set_latency (base_video_encoder, latency, latency);

  schro_video_format_set_std_colour_spec (schro_enc->video_format,
      SCHRO_COLOUR_SPEC_HDTV);

  schro_encoder_set_video_format (schro_enc->encoder, schro_enc->video_format);
  schro_encoder_start (schro_enc->encoder);

  seq_header_buffer =
      gst_schro_wrap_schro_buffer (schro_encoder_encode_sequence_header
      (schro_enc->encoder));

  schro_enc->granule_offset = ~0;

  profile = schro_encoder_setting_get_double (schro_enc->encoder, "profile");
  level = schro_encoder_setting_get_double (schro_enc->encoder, "level");

  out_caps = gst_caps_new_simple ("video/x-dirac",
      "profile", G_TYPE_STRING, get_profile_name (profile),
      "level", G_TYPE_STRING, get_level_name (level), NULL);

  output_state =
      gst_video_encoder_set_output_state (base_video_encoder, out_caps, state);

  GST_BUFFER_FLAG_SET (seq_header_buffer, GST_BUFFER_FLAG_HEADER);
  {
    GValue array = { 0 };
    GValue value = { 0 };
    guint8 *outdata;
    GstBuffer *buf;
    GstMemory *seq_header_memory, *extra_header;
    gsize size;

    g_value_init (&array, GST_TYPE_ARRAY);
    g_value_init (&value, GST_TYPE_BUFFER);

    buf = gst_buffer_new ();
    /* Add the sequence header */
    seq_header_memory = gst_buffer_get_memory (seq_header_buffer, 0);
    gst_buffer_append_memory (buf, seq_header_memory);

    size = gst_buffer_get_size (buf) + SCHRO_PARSE_HEADER_SIZE;
    outdata = g_malloc0 (SCHRO_PARSE_HEADER_SIZE);

    GST_WRITE_UINT32_BE (outdata, 0x42424344);
    GST_WRITE_UINT8 (outdata + 4, SCHRO_PARSE_CODE_END_OF_SEQUENCE);
    GST_WRITE_UINT32_BE (outdata + 5, 0);
    GST_WRITE_UINT32_BE (outdata + 9, size);

    extra_header = gst_memory_new_wrapped (0, outdata, SCHRO_PARSE_HEADER_SIZE,
        0, SCHRO_PARSE_HEADER_SIZE, outdata, g_free);
    gst_buffer_append_memory (buf, extra_header);

    /* ogg(mux) expects the header buffers to have 0 timestamps -
       set OFFSET and OFFSET_END accordingly */
    GST_BUFFER_OFFSET (buf) = 0;
    GST_BUFFER_OFFSET_END (buf) = 0;
    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);

    gst_value_set_buffer (&value, buf);
    gst_buffer_unref (buf);
    gst_value_array_append_value (&array, &value);
    gst_structure_set_value (gst_caps_get_structure (output_state->caps, 0),
        "streamheader", &array);
    g_value_unset (&value);
    g_value_unset (&array);
  }
  gst_buffer_unref (seq_header_buffer);

  gst_video_codec_state_unref (output_state);

  /* And save the input state for later use */
  if (schro_enc->input_state)
    gst_video_codec_state_unref (schro_enc->input_state);
  schro_enc->input_state = gst_video_codec_state_ref (state);

  return TRUE;
}

static void
gst_schro_enc_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstSchroEnc *src;

  src = GST_SCHRO_ENC (object);

  GST_DEBUG ("%s", pspec->name);

  if (prop_id >= 1) {
    const SchroEncoderSetting *setting;
    setting = schro_encoder_get_setting_info (prop_id - 1);
    switch (G_VALUE_TYPE (value)) {
      case G_TYPE_DOUBLE:
        schro_encoder_setting_set_double (src->encoder, setting->name,
            g_value_get_double (value));
        break;
      case G_TYPE_INT:
        schro_encoder_setting_set_double (src->encoder, setting->name,
            g_value_get_int (value));
        break;
      case G_TYPE_BOOLEAN:
        schro_encoder_setting_set_double (src->encoder, setting->name,
            g_value_get_boolean (value));
        break;
      default:
        schro_encoder_setting_set_double (src->encoder, setting->name,
            g_value_get_enum (value));
        break;
    }
  }
}

static void
gst_schro_enc_get_property (GObject * object, guint prop_id, GValue * value,
    GParamSpec * pspec)
{
  GstSchroEnc *src;

  src = GST_SCHRO_ENC (object);

  if (prop_id >= 1) {
    const SchroEncoderSetting *setting;
    setting = schro_encoder_get_setting_info (prop_id - 1);
    switch (G_VALUE_TYPE (value)) {
      case G_TYPE_DOUBLE:
        g_value_set_double (value,
            schro_encoder_setting_get_double (src->encoder, setting->name));
        break;
      case G_TYPE_INT:
        g_value_set_int (value,
            schro_encoder_setting_get_double (src->encoder, setting->name));
        break;
      case G_TYPE_BOOLEAN:
        g_value_set_boolean (value,
            schro_encoder_setting_get_double (src->encoder, setting->name));
        break;
      default:
        /* it's an enum */
        g_value_set_enum (value,
            schro_encoder_setting_get_double (src->encoder, setting->name));
        break;
    }
  }
}

static gboolean
gst_schro_enc_start (GstVideoEncoder * base_video_encoder)
{
  return TRUE;
}

static gboolean
gst_schro_enc_stop (GstVideoEncoder * base_video_encoder)
{
  return TRUE;
}

static GstFlowReturn
gst_schro_enc_finish (GstVideoEncoder * base_video_encoder)
{
  GstSchroEnc *schro_enc = GST_SCHRO_ENC (base_video_encoder);

  GST_DEBUG ("finish");

  schro_encoder_end_of_stream (schro_enc->encoder);
  gst_schro_enc_process (schro_enc);

  return GST_FLOW_OK;
}

static GstFlowReturn
gst_schro_enc_handle_frame (GstVideoEncoder * base_video_encoder,
    GstVideoCodecFrame * frame)
{
  GstSchroEnc *schro_enc = GST_SCHRO_ENC (base_video_encoder);
  SchroFrame *schro_frame;
  GstFlowReturn ret;
  GstVideoInfo *info = &schro_enc->input_state->info;

  if (schro_enc->granule_offset == ~0LL) {
    schro_enc->granule_offset =
        gst_util_uint64_scale (frame->pts, 2 * GST_VIDEO_INFO_FPS_N (info),
        GST_SECOND * GST_VIDEO_INFO_FPS_D (info));
    GST_DEBUG ("granule offset %" G_GINT64_FORMAT, schro_enc->granule_offset);
  }

  schro_frame = gst_schro_buffer_wrap (frame->input_buffer, FALSE, info);

  GST_DEBUG ("pushing frame %p", frame);
  schro_encoder_push_frame_full (schro_enc->encoder, schro_frame, frame);

  ret = gst_schro_enc_process (schro_enc);

  return ret;
}

static GstFlowReturn
gst_schro_enc_pre_push (GstVideoEncoder * base_video_encoder,
    GstVideoCodecFrame * frame)
{
  GstSchroEnc *schro_enc;
  int delay;
  int dist;
  int pt;
  int dt;
  guint64 granulepos_hi;
  guint64 granulepos_low;
  GstBuffer *buf = frame->output_buffer;

  schro_enc = GST_SCHRO_ENC (base_video_encoder);

  pt = frame->presentation_frame_number * 2 + schro_enc->granule_offset;
  dt = frame->decode_frame_number * 2 + schro_enc->granule_offset;
  delay = pt - dt;
  dist = frame->distance_from_sync;

  GST_DEBUG ("sys %d dpn %d pt %d dt %d delay %d dist %d",
      (int) frame->system_frame_number,
      (int) frame->decode_frame_number, pt, dt, delay, dist);

  granulepos_hi = (((guint64) pt - delay) << 9) | ((dist >> 8));
  granulepos_low = (delay << 9) | (dist & 0xff);
  GST_DEBUG ("granulepos %" G_GINT64_FORMAT ":%" G_GINT64_FORMAT, granulepos_hi,
      granulepos_low);

#if 0
  if (frame->is_eos) {
    GST_BUFFER_OFFSET_END (buf) = schro_enc->last_granulepos;
  } else {
#endif
    schro_enc->last_granulepos = (granulepos_hi << 22) | (granulepos_low);
    GST_BUFFER_OFFSET_END (buf) = schro_enc->last_granulepos;
#if 0
  }
#endif

  GST_BUFFER_OFFSET (buf) = gst_util_uint64_scale (schro_enc->last_granulepos,
      GST_SECOND * schro_enc->video_format->frame_rate_denominator,
      schro_enc->video_format->frame_rate_numerator);

  return GST_FLOW_OK;
}

static gboolean
gst_schro_enc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
{
  gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);

  return GST_VIDEO_ENCODER_CLASS (parent_class)->propose_allocation (encoder,
      query);
}


static GstFlowReturn
gst_schro_enc_process (GstSchroEnc * schro_enc)
{
  SchroBuffer *encoded_buffer;
  GstVideoCodecFrame *frame;
  GstFlowReturn ret;
  int presentation_frame;
  void *voidptr;
  GstVideoEncoder *base_video_encoder = GST_VIDEO_ENCODER (schro_enc);

  GST_DEBUG ("process");

  while (1) {
    switch (schro_encoder_wait (schro_enc->encoder)) {
      case SCHRO_STATE_NEED_FRAME:
        return GST_FLOW_OK;
      case SCHRO_STATE_END_OF_STREAM:
        GST_DEBUG ("EOS");
        return GST_FLOW_OK;
      case SCHRO_STATE_HAVE_BUFFER:
        voidptr = NULL;
        encoded_buffer = schro_encoder_pull_full (schro_enc->encoder,
            &presentation_frame, &voidptr);
        frame = voidptr;
        if (encoded_buffer == NULL) {
          GST_DEBUG ("encoder_pull returned NULL");
          /* FIXME This shouldn't happen */
          return GST_FLOW_ERROR;
        }
#if SCHRO_CHECK_VERSION (1, 0, 9)
        {
          GstMessage *message;
          GstStructure *structure;
          GstBuffer *buf;
          gpointer data;

          data = g_malloc (sizeof (double) * 21);
          schro_encoder_get_frame_stats (schro_enc->encoder,
              (double *) data, 21);
          buf = gst_buffer_new_wrapped (data, sizeof (double) * 21);
          structure = gst_structure_new ("GstSchroEnc",
              "frame-stats", GST_TYPE_BUFFER, buf, NULL);
          gst_buffer_unref (buf);
          message = gst_message_new_element (GST_OBJECT (schro_enc), structure);
          gst_element_post_message (GST_ELEMENT (schro_enc), message);
        }
#endif

        if (voidptr == NULL) {
          GST_DEBUG ("got eos");
          //frame = schro_enc->eos_frame;
          frame = NULL;
          schro_buffer_unref (encoded_buffer);
        }

        /* FIXME: Get the frame from somewhere somehow... */
        if (frame) {
          if (SCHRO_PARSE_CODE_IS_SEQ_HEADER (encoded_buffer->data[4])) {
            GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
          }

          frame->output_buffer = gst_schro_wrap_schro_buffer (encoded_buffer);

          ret = gst_video_encoder_finish_frame (base_video_encoder, frame);

          if (ret != GST_FLOW_OK) {
            GST_DEBUG ("pad_push returned %d", ret);
            return ret;
          }
        }
        break;
      case SCHRO_STATE_AGAIN:
        break;
    }
  }
  return GST_FLOW_OK;
}