/* 
 * Copyright (C) 2012 Collabora Ltd.
 *     Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
 * Copyright (C) 2013 Sebastian Dröge <slomo@circular-chaos.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.
 *
 */

/**
 * SECTION:element-openjpegenc
 * @title: openjpegenc
 * @see_also: openjpegdec
 *
 * openjpegenc encodes raw video stream.
 *
 * ## Example launch lines
 * |[
 * gst-launch-1.0 -v videotestsrc num-buffers=10 ! openjpegenc ! jpeg2000parse ! openjpegdec ! videoconvert ! autovideosink sync=false
 * ]| Encode and decode whole frames.
 * |[
 * gst-launch-1.0 -v videotestsrc num-buffers=10 ! openjpegenc num-threads=8 num-stripes=8 ! jpeg2000parse ! openjpegdec max-threads=8 ! videoconvert ! autovideosink sync=fals
 * ]| Encode and decode frame split with stripes.
 *
 */

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

#include "gstopenjpegenc.h"
#include <gst/codecparsers/gstjpeg2000sampling.h>

#include <string.h>
#include <math.h>

GST_DEBUG_CATEGORY_STATIC (gst_openjpeg_enc_debug);
#define GST_CAT_DEFAULT gst_openjpeg_enc_debug

#define GST_OPENJPEG_ENC_TYPE_PROGRESSION_ORDER (gst_openjpeg_enc_progression_order_get_type())
static GType
gst_openjpeg_enc_progression_order_get_type (void)
{
  static const GEnumValue values[] = {
    {OPJ_LRCP, "LRCP", "lrcp"},
    {OPJ_RLCP, "RLCP", "rlcp"},
    {OPJ_RPCL, "RPCL", "rpcl"},
    {OPJ_PCRL, "PCRL", "pcrl"},
    {OPJ_CPRL, "CPRL", "crpl"},
    {0, NULL, NULL}
  };
  static GType id = 0;

  if (g_once_init_enter ((gsize *) & id)) {
    GType _id;

    _id = g_enum_register_static ("GstOpenJPEGEncProgressionOrder", values);

    g_once_init_leave ((gsize *) & id, _id);
  }

  return id;
}

enum
{
  PROP_0,
  PROP_NUM_LAYERS,
  PROP_NUM_RESOLUTIONS,
  PROP_PROGRESSION_ORDER,
  PROP_TILE_OFFSET_X,
  PROP_TILE_OFFSET_Y,
  PROP_TILE_WIDTH,
  PROP_TILE_HEIGHT,
  PROP_NUM_STRIPES,
  PROP_NUM_THREADS,
  PROP_LAST
};


#define DEFAULT_NUM_LAYERS 1
#define DEFAULT_NUM_RESOLUTIONS 6
#define DEFAULT_PROGRESSION_ORDER OPJ_LRCP
#define DEFAULT_TILE_OFFSET_X 0
#define DEFAULT_TILE_OFFSET_Y 0
#define DEFAULT_TILE_WIDTH 0
#define DEFAULT_TILE_HEIGHT 0
#define GST_OPENJPEG_ENC_DEFAULT_NUM_STRIPES  1
#define GST_OPENJPEG_ENC_DEFAULT_NUM_THREADS 0

/* prototypes */
static void gst_openjpeg_enc_finalize (GObject * object);

static GstStateChangeReturn
gst_openjpeg_enc_change_state (GstElement * element, GstStateChange transition);

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

static gboolean gst_openjpeg_enc_start (GstVideoEncoder * encoder);
static gboolean gst_openjpeg_enc_stop (GstVideoEncoder * encoder);
static gboolean gst_openjpeg_enc_set_format (GstVideoEncoder * encoder,
    GstVideoCodecState * state);
static GstFlowReturn gst_openjpeg_enc_handle_frame (GstVideoEncoder * encoder,
    GstVideoCodecFrame * frame);
static gboolean gst_openjpeg_enc_propose_allocation (GstVideoEncoder * encoder,
    GstQuery * query);
static GstFlowReturn gst_openjpeg_enc_encode_frame_multiple (GstVideoEncoder *
    encoder, GstVideoCodecFrame * frame);
static GstFlowReturn gst_openjpeg_enc_encode_frame_single (GstVideoEncoder *
    encoder, GstVideoCodecFrame * frame);
static GstOpenJPEGCodecMessage
    * gst_openjpeg_encode_message_free (GstOpenJPEGCodecMessage * message);

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GRAY16 "GRAY16_LE"
#define YUV10 "Y444_10LE, I422_10LE, I420_10LE"
#else
#define GRAY16 "GRAY16_BE"
#define YUV10 "Y444_10BE, I422_10BE, I420_10BE"
#endif

static GstStaticPadTemplate gst_openjpeg_enc_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ ARGB64, ARGB, xRGB, "
            "AYUV64, " YUV10 ", "
            "AYUV, Y444, Y42B, I420, Y41B, YUV9, " "GRAY8, " GRAY16 " }"))
    );

static GstStaticPadTemplate gst_openjpeg_enc_src_template =
    GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("image/x-j2c, "
        "width = (int) [1, MAX], "
        "height = (int) [1, MAX], "
        "num-components = (int) [1, 4], "
        GST_JPEG2000_SAMPLING_LIST ","
        GST_JPEG2000_COLORSPACE_LIST "; "
        "image/x-jpc, "
        "width = (int) [1, MAX], "
        "height = (int) [1, MAX], "
        "num-components = (int) [1, 4], "
        "num-stripes = (int) [1, MAX], "
        "alignment = (string) { frame, stripe }, "
        GST_JPEG2000_SAMPLING_LIST ","
        GST_JPEG2000_COLORSPACE_LIST "; "
        "image/jp2, " "width = (int) [1, MAX], "
        "height = (int) [1, MAX] ;"
        "image/x-jpc-striped, "
        "width = (int) [1, MAX], "
        "height = (int) [1, MAX], "
        "num-components = (int) [1, 4], "
        GST_JPEG2000_SAMPLING_LIST ", "
        GST_JPEG2000_COLORSPACE_LIST ", "
        "num-stripes = (int) [2, MAX], stripe-height = (int) [1 , MAX]")
    );

#define parent_class gst_openjpeg_enc_parent_class
G_DEFINE_TYPE (GstOpenJPEGEnc, gst_openjpeg_enc, GST_TYPE_VIDEO_ENCODER);
GST_ELEMENT_REGISTER_DEFINE (openjpegenc, "openjpegenc",
    GST_RANK_PRIMARY, GST_TYPE_OPENJPEG_ENC);

static void
gst_openjpeg_enc_class_init (GstOpenJPEGEncClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *element_class;
  GstVideoEncoderClass *video_encoder_class;

  gobject_class = (GObjectClass *) klass;
  element_class = (GstElementClass *) klass;
  video_encoder_class = (GstVideoEncoderClass *) klass;

  gobject_class->set_property = gst_openjpeg_enc_set_property;
  gobject_class->get_property = gst_openjpeg_enc_get_property;
  gobject_class->finalize = gst_openjpeg_enc_finalize;

  element_class->change_state =
      GST_DEBUG_FUNCPTR (gst_openjpeg_enc_change_state);

  g_object_class_install_property (gobject_class, PROP_NUM_LAYERS,
      g_param_spec_int ("num-layers", "Number of layers",
          "Number of layers", 1, 10, DEFAULT_NUM_LAYERS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_NUM_RESOLUTIONS,
      g_param_spec_int ("num-resolutions", "Number of resolutions",
          "Number of resolutions", 1, 10, DEFAULT_NUM_RESOLUTIONS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_PROGRESSION_ORDER,
      g_param_spec_enum ("progression-order", "Progression Order",
          "Progression order", GST_OPENJPEG_ENC_TYPE_PROGRESSION_ORDER,
          DEFAULT_PROGRESSION_ORDER,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TILE_OFFSET_X,
      g_param_spec_int ("tile-offset-x", "Tile Offset X",
          "Tile Offset X", G_MININT, G_MAXINT, DEFAULT_TILE_OFFSET_X,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TILE_OFFSET_Y,
      g_param_spec_int ("tile-offset-y", "Tile Offset Y",
          "Tile Offset Y", G_MININT, G_MAXINT, DEFAULT_TILE_OFFSET_Y,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TILE_WIDTH,
      g_param_spec_int ("tile-width", "Tile Width",
          "Tile Width", 0, G_MAXINT, DEFAULT_TILE_WIDTH,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_TILE_HEIGHT,
      g_param_spec_int ("tile-height", "Tile Height",
          "Tile Height", 0, G_MAXINT, DEFAULT_TILE_HEIGHT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstOpenJPEGEnc:num-stripes:
   *
   * Number of stripes to use for low latency encoding . (1 = low latency disabled)
   *
   * Since: 1.18
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_NUM_STRIPES,
      g_param_spec_int ("num-stripes", "Number of stripes",
          "Number of stripes for low latency encoding. (1 = low latency disabled)",
          1, G_MAXINT, GST_OPENJPEG_ENC_DEFAULT_NUM_STRIPES,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /**
   * GstOpenJPEGEnc:num-threads:
   *
   * Max number of simultaneous threads to encode stripes, default: encode with streaming thread
   *
   * Since: 1.20
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_NUM_THREADS,
      g_param_spec_uint ("num-threads", "Number of threads",
          "Max number of simultaneous threads to encode stripe or frame, default: encode with streaming thread.",
          0, G_MAXINT, GST_OPENJPEG_ENC_DEFAULT_NUM_THREADS,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gst_element_class_add_static_pad_template (element_class,
      &gst_openjpeg_enc_src_template);
  gst_element_class_add_static_pad_template (element_class,
      &gst_openjpeg_enc_sink_template);

  gst_element_class_set_static_metadata (element_class,
      "OpenJPEG JPEG2000 encoder",
      "Codec/Encoder/Video",
      "Encode JPEG2000 streams",
      "Sebastian Dröge <sebastian.droege@collabora.co.uk>");

  video_encoder_class->start = GST_DEBUG_FUNCPTR (gst_openjpeg_enc_start);
  video_encoder_class->stop = GST_DEBUG_FUNCPTR (gst_openjpeg_enc_stop);
  video_encoder_class->set_format =
      GST_DEBUG_FUNCPTR (gst_openjpeg_enc_set_format);
  video_encoder_class->handle_frame =
      GST_DEBUG_FUNCPTR (gst_openjpeg_enc_handle_frame);
  video_encoder_class->propose_allocation = gst_openjpeg_enc_propose_allocation;

  GST_DEBUG_CATEGORY_INIT (gst_openjpeg_enc_debug, "openjpegenc", 0,
      "OpenJPEG Encoder");

  gst_type_mark_as_plugin_api (GST_OPENJPEG_ENC_TYPE_PROGRESSION_ORDER, 0);
}

static void
gst_openjpeg_enc_init (GstOpenJPEGEnc * self)
{
  GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_ENCODER_SINK_PAD (self));

  opj_set_default_encoder_parameters (&self->params);

  self->params.cp_fixed_quality = 1;
  self->params.cp_disto_alloc = 0;
  self->params.cp_fixed_alloc = 0;

  /*
   * TODO: Add properties / caps fields for these
   *
   * self->params.csty;
   * self->params.tcp_rates;
   * self->params.tcp_distoratio;
   * self->params.mode;
   * self->params.irreversible;
   * self->params.cp_cinema;
   * self->params.cp_rsiz;
   */

  self->params.tcp_numlayers = DEFAULT_NUM_LAYERS;
  self->params.numresolution = DEFAULT_NUM_RESOLUTIONS;
  self->params.prog_order = DEFAULT_PROGRESSION_ORDER;
  self->params.cp_tx0 = DEFAULT_TILE_OFFSET_X;
  self->params.cp_ty0 = DEFAULT_TILE_OFFSET_Y;
  self->params.cp_tdx = DEFAULT_TILE_WIDTH;
  self->params.cp_tdy = DEFAULT_TILE_HEIGHT;
  self->params.tile_size_on = (self->params.cp_tdx != 0
      && self->params.cp_tdy != 0);

  self->num_stripes = GST_OPENJPEG_ENC_DEFAULT_NUM_STRIPES;
  g_cond_init (&self->messages_cond);
  g_queue_init (&self->messages);

  self->available_threads = GST_OPENJPEG_ENC_DEFAULT_NUM_THREADS;
}

static void
gst_openjpeg_enc_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (object);

  switch (prop_id) {
    case PROP_NUM_LAYERS:
      self->params.tcp_numlayers = g_value_get_int (value);
      break;
    case PROP_NUM_RESOLUTIONS:
      self->params.numresolution = g_value_get_int (value);
      break;
    case PROP_PROGRESSION_ORDER:
      self->params.prog_order = g_value_get_enum (value);
      break;
    case PROP_TILE_OFFSET_X:
      self->params.cp_tx0 = g_value_get_int (value);
      break;
    case PROP_TILE_OFFSET_Y:
      self->params.cp_ty0 = g_value_get_int (value);
      break;
    case PROP_TILE_WIDTH:
      self->params.cp_tdx = g_value_get_int (value);
      self->params.tile_size_on = (self->params.cp_tdx != 0
          && self->params.cp_tdy != 0);
      break;
    case PROP_TILE_HEIGHT:
      self->params.cp_tdy = g_value_get_int (value);
      self->params.tile_size_on = (self->params.cp_tdx != 0
          && self->params.cp_tdy != 0);
      break;
    case PROP_NUM_STRIPES:
      self->num_stripes = g_value_get_int (value);
      break;
    case PROP_NUM_THREADS:
      self->available_threads = g_value_get_uint (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_openjpeg_enc_get_property (GObject * object, guint prop_id, GValue * value,
    GParamSpec * pspec)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (object);

  switch (prop_id) {
    case PROP_NUM_LAYERS:
      g_value_set_int (value, self->params.tcp_numlayers);
      break;
    case PROP_NUM_RESOLUTIONS:
      g_value_set_int (value, self->params.numresolution);
      break;
    case PROP_PROGRESSION_ORDER:
      g_value_set_enum (value, self->params.prog_order);
      break;
    case PROP_TILE_OFFSET_X:
      g_value_set_int (value, self->params.cp_tx0);
      break;
    case PROP_TILE_OFFSET_Y:
      g_value_set_int (value, self->params.cp_ty0);
      break;
    case PROP_TILE_WIDTH:
      g_value_set_int (value, self->params.cp_tdx);
      break;
    case PROP_TILE_HEIGHT:
      g_value_set_int (value, self->params.cp_tdy);
      break;
    case PROP_NUM_STRIPES:
      g_value_set_int (value, self->num_stripes);
      break;
    case PROP_NUM_THREADS:
      g_value_set_uint (value, self->available_threads);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static gboolean
gst_openjpeg_enc_start (GstVideoEncoder * encoder)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (encoder);

  GST_DEBUG_OBJECT (self, "Starting");
  if (self->available_threads)
    self->encode_frame = gst_openjpeg_enc_encode_frame_multiple;
  else
    self->encode_frame = gst_openjpeg_enc_encode_frame_single;

  return TRUE;
}

static gboolean
gst_openjpeg_enc_stop (GstVideoEncoder * video_encoder)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (video_encoder);

  GST_DEBUG_OBJECT (self, "Stopping");

  if (self->output_state) {
    gst_video_codec_state_unref (self->output_state);
    self->output_state = NULL;
  }

  if (self->input_state) {
    gst_video_codec_state_unref (self->input_state);
    self->input_state = NULL;
  }

  GST_DEBUG_OBJECT (self, "Stopped");

  return TRUE;
}

static void
gst_openjpeg_enc_flush_messages (GstOpenJPEGEnc * self)
{
  GstOpenJPEGCodecMessage *enc_params;

  GST_OBJECT_LOCK (self);
  while ((enc_params = g_queue_pop_head (&self->messages))) {
    gst_openjpeg_encode_message_free (enc_params);
  }
  g_cond_broadcast (&self->messages_cond);
  GST_OBJECT_UNLOCK (self);
}

static void
gst_openjpeg_enc_finalize (GObject * object)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (object);

  gst_openjpeg_enc_flush_messages (self);
  g_cond_clear (&self->messages_cond);

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

static GstStateChangeReturn
gst_openjpeg_enc_change_state (GstElement * element, GstStateChange transition)
{
  GstOpenJPEGEnc *self;

  g_return_val_if_fail (GST_IS_OPENJPEG_ENC (element),
      GST_STATE_CHANGE_FAILURE);
  self = GST_OPENJPEG_ENC (element);

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
      break;
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      break;
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
      break;
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_openjpeg_enc_flush_messages (self);
      break;
    default:
      break;
  }

  return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
}

static guint
get_stripe_height (GstOpenJPEGEnc * self, guint slice_num, guint frame_height)
{
  guint nominal_stripe_height = frame_height / self->num_stripes;
  return (slice_num <
      self->num_stripes -
      1) ? nominal_stripe_height : frame_height -
      (slice_num * nominal_stripe_height);
}

static void
fill_image_packed16_4 (opj_image_t * image, GstVideoFrame * frame)
{
  gint x, y, w, h;
  const guint16 *data_in, *tmp;
  gint *data_out[4];
  gint sstride;

  w = GST_VIDEO_FRAME_WIDTH (frame);
  h = image->y1 - image->y0;
  sstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) / 2;
  data_in =
      (guint16 *) GST_VIDEO_FRAME_PLANE_DATA (frame, 0) + image->y0 * sstride;

  data_out[0] = image->comps[0].data;
  data_out[1] = image->comps[1].data;
  data_out[2] = image->comps[2].data;
  data_out[3] = image->comps[3].data;

  for (y = 0; y < h; y++) {
    tmp = data_in;

    for (x = 0; x < w; x++) {
      *data_out[3] = tmp[0];
      *data_out[0] = tmp[1];
      *data_out[1] = tmp[2];
      *data_out[2] = tmp[3];

      tmp += 4;
      data_out[0]++;
      data_out[1]++;
      data_out[2]++;
      data_out[3]++;
    }
    data_in += sstride;
  }
}

static void
fill_image_packed8_4 (opj_image_t * image, GstVideoFrame * frame)
{
  gint x, y, w, h;
  const guint8 *data_in, *tmp;
  gint *data_out[4];
  gint sstride;

  w = GST_VIDEO_FRAME_WIDTH (frame);
  h = image->y1 - image->y0;
  sstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
  data_in =
      (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (frame, 0) + image->y0 * sstride;

  data_out[0] = image->comps[0].data;
  data_out[1] = image->comps[1].data;
  data_out[2] = image->comps[2].data;
  data_out[3] = image->comps[3].data;

  for (y = 0; y < h; y++) {
    tmp = data_in;

    for (x = 0; x < w; x++) {
      *data_out[3] = tmp[0];
      *data_out[0] = tmp[1];
      *data_out[1] = tmp[2];
      *data_out[2] = tmp[3];

      tmp += 4;
      data_out[0]++;
      data_out[1]++;
      data_out[2]++;
      data_out[3]++;
    }
    data_in += sstride;
  }
}

static void
fill_image_packed8_3 (opj_image_t * image, GstVideoFrame * frame)
{
  gint x, y, w, h;
  const guint8 *data_in, *tmp;
  gint *data_out[3];
  gint sstride;

  w = GST_VIDEO_FRAME_WIDTH (frame);
  h = image->y1 - image->y0;
  sstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
  data_in =
      (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (frame, 0) + image->y0 * sstride;

  data_out[0] = image->comps[0].data;
  data_out[1] = image->comps[1].data;
  data_out[2] = image->comps[2].data;

  for (y = 0; y < h; y++) {
    tmp = data_in;

    for (x = 0; x < w; x++) {
      *data_out[0] = tmp[1];
      *data_out[1] = tmp[2];
      *data_out[2] = tmp[3];

      tmp += 4;
      data_out[0]++;
      data_out[1]++;
      data_out[2]++;
    }
    data_in += sstride;
  }
}

static void
fill_image_planar16_3 (opj_image_t * image, GstVideoFrame * frame)
{
  gint c, x, y, w, h;
  const guint16 *data_in, *tmp;
  gint *data_out;
  gint sstride;

  for (c = 0; c < 3; c++) {
    opj_image_comp_t *comp = image->comps + c;

    w = GST_VIDEO_FRAME_COMP_WIDTH (frame, c);
    h = comp->h;
    sstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, c) / 2;
    data_in =
        (guint16 *) GST_VIDEO_FRAME_COMP_DATA (frame,
        c) + (image->y0 / comp->dy) * sstride;
    data_out = comp->data;

    for (y = 0; y < h; y++) {
      tmp = data_in;
      for (x = 0; x < w; x++) {
        *data_out = *tmp;
        data_out++;
        tmp++;
      }
      data_in += sstride;
    }
  }
}

static void
fill_image_planar8_3 (opj_image_t * image, GstVideoFrame * frame)
{
  gint c, x, y, w, h;
  const guint8 *data_in, *tmp;
  gint *data_out;
  gint sstride;

  for (c = 0; c < 3; c++) {
    opj_image_comp_t *comp = image->comps + c;

    w = GST_VIDEO_FRAME_COMP_WIDTH (frame, c);
    h = comp->h;
    sstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, c);
    data_in =
        (guint8 *) GST_VIDEO_FRAME_COMP_DATA (frame,
        c) + (image->y0 / comp->dy) * sstride;
    data_out = comp->data;

    for (y = 0; y < h; y++) {
      tmp = data_in;
      for (x = 0; x < w; x++) {
        *data_out = *tmp;
        data_out++;
        tmp++;
      }
      data_in += sstride;
    }
  }
}

static void
fill_image_planar8_1 (opj_image_t * image, GstVideoFrame * frame)
{
  gint x, y, w, h;
  const guint8 *data_in, *tmp;
  gint *data_out;
  gint sstride;
  opj_image_comp_t *comp = image->comps;

  w = GST_VIDEO_FRAME_COMP_WIDTH (frame, 0);
  h = comp->h;
  sstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
  data_in =
      (guint8 *) GST_VIDEO_FRAME_COMP_DATA (frame,
      0) + (image->y0 / comp->dy) * sstride;
  data_out = image->comps[0].data;

  for (y = 0; y < h; y++) {
    tmp = data_in;
    for (x = 0; x < w; x++) {
      *data_out = *tmp;
      data_out++;
      tmp++;
    }
    data_in += sstride;
  }
}

static void
fill_image_planar16_1 (opj_image_t * image, GstVideoFrame * frame)
{
  gint x, y, w, h;
  const guint16 *data_in, *tmp;
  gint *data_out;
  gint sstride;
  opj_image_comp_t *comp = image->comps;

  w = GST_VIDEO_FRAME_COMP_WIDTH (frame, 0);
  h = comp->h;
  sstride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) / 2;
  data_in =
      (guint16 *) GST_VIDEO_FRAME_COMP_DATA (frame,
      0) + (image->y0 / comp->dy) * sstride;
  data_out = comp->data;

  for (y = 0; y < h; y++) {
    tmp = data_in;
    for (x = 0; x < w; x++) {
      *data_out = *tmp;
      data_out++;
      tmp++;
    }
    data_in += sstride;
  }
}

static gboolean
gst_openjpeg_enc_set_format (GstVideoEncoder * encoder,
    GstVideoCodecState * state)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (encoder);
  GstCaps *allowed_caps, *caps;
  GstStructure *s;
  const gchar *colorspace = NULL;
  GstJPEG2000Sampling sampling = GST_JPEG2000_SAMPLING_NONE;
  gint ncomps;
  gboolean stripe_mode =
      self->num_stripes != GST_OPENJPEG_ENC_DEFAULT_NUM_STRIPES;

  GST_DEBUG_OBJECT (self, "Setting format: %" GST_PTR_FORMAT, state->caps);

  if (self->input_state)
    gst_video_codec_state_unref (self->input_state);
  self->input_state = gst_video_codec_state_ref (state);

  if (stripe_mode) {
    GstCaps *template_caps = gst_caps_new_empty_simple ("image/x-jpc-striped");
    GstCaps *my_caps;

    my_caps = gst_pad_query_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder),
        template_caps);
    gst_caps_unref (template_caps);

    allowed_caps = gst_pad_peer_query_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder),
        my_caps);
    gst_caps_unref (my_caps);

    if (gst_caps_is_empty (allowed_caps)) {
      gst_caps_unref (allowed_caps);
      GST_WARNING_OBJECT (self, "Striped JPEG 2000 not accepted downstream");
      return FALSE;
    }

    self->codec_format = OPJ_CODEC_J2K;
    self->is_jp2c = FALSE;
    allowed_caps = gst_caps_truncate (allowed_caps);
    s = gst_caps_get_structure (allowed_caps, 0);
  } else {
    allowed_caps =
        gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
    allowed_caps = gst_caps_truncate (allowed_caps);

    s = gst_caps_get_structure (allowed_caps, 0);
    if (gst_structure_has_name (s, "image/jp2")) {
      self->codec_format = OPJ_CODEC_JP2;
      self->is_jp2c = FALSE;
    } else if (gst_structure_has_name (s, "image/x-j2c")) {
      self->codec_format = OPJ_CODEC_J2K;
      self->is_jp2c = TRUE;
    } else if (gst_structure_has_name (s, "image/x-jpc")) {
      self->codec_format = OPJ_CODEC_J2K;
      self->is_jp2c = FALSE;
    } else {
      g_return_val_if_reached (FALSE);
    }
  }

  switch (state->info.finfo->format) {
    case GST_VIDEO_FORMAT_ARGB64:
      self->fill_image = fill_image_packed16_4;
      ncomps = 4;
      break;
    case GST_VIDEO_FORMAT_ARGB:
    case GST_VIDEO_FORMAT_AYUV:
      self->fill_image = fill_image_packed8_4;
      ncomps = 4;
      break;
    case GST_VIDEO_FORMAT_xRGB:
      self->fill_image = fill_image_packed8_3;
      ncomps = 3;
      break;
    case GST_VIDEO_FORMAT_AYUV64:
      self->fill_image = fill_image_packed16_4;
      ncomps = 4;
      break;
    case GST_VIDEO_FORMAT_Y444_10LE:
    case GST_VIDEO_FORMAT_Y444_10BE:
    case GST_VIDEO_FORMAT_I422_10LE:
    case GST_VIDEO_FORMAT_I422_10BE:
    case GST_VIDEO_FORMAT_I420_10LE:
    case GST_VIDEO_FORMAT_I420_10BE:
      self->fill_image = fill_image_planar16_3;
      ncomps = 3;
      break;
    case GST_VIDEO_FORMAT_Y444:
    case GST_VIDEO_FORMAT_Y42B:
    case GST_VIDEO_FORMAT_I420:
    case GST_VIDEO_FORMAT_Y41B:
    case GST_VIDEO_FORMAT_YUV9:
      self->fill_image = fill_image_planar8_3;
      ncomps = 3;
      break;
    case GST_VIDEO_FORMAT_GRAY8:
      self->fill_image = fill_image_planar8_1;
      ncomps = 1;
      break;
    case GST_VIDEO_FORMAT_GRAY16_LE:
    case GST_VIDEO_FORMAT_GRAY16_BE:
      self->fill_image = fill_image_planar16_1;
      ncomps = 1;
      break;
    default:
      g_assert_not_reached ();
  }

  /* sampling */
  /* note: encoder re-orders channels so that alpha channel is encoded as the last channel */
  switch (state->info.finfo->format) {
    case GST_VIDEO_FORMAT_ARGB64:
    case GST_VIDEO_FORMAT_ARGB:
      sampling = GST_JPEG2000_SAMPLING_RGBA;
      break;
    case GST_VIDEO_FORMAT_AYUV64:
    case GST_VIDEO_FORMAT_AYUV:
      sampling = GST_JPEG2000_SAMPLING_YBRA4444_EXT;
      break;
    case GST_VIDEO_FORMAT_xRGB:
      sampling = GST_JPEG2000_SAMPLING_RGB;
      break;
    case GST_VIDEO_FORMAT_Y444_10LE:
    case GST_VIDEO_FORMAT_Y444_10BE:
    case GST_VIDEO_FORMAT_Y444:
      sampling = GST_JPEG2000_SAMPLING_YBR444;
      break;

    case GST_VIDEO_FORMAT_I422_10LE:
    case GST_VIDEO_FORMAT_I422_10BE:
    case GST_VIDEO_FORMAT_Y42B:
      sampling = GST_JPEG2000_SAMPLING_YBR422;
      break;
    case GST_VIDEO_FORMAT_YUV9:
      sampling = GST_JPEG2000_SAMPLING_YBR410;
      break;
    case GST_VIDEO_FORMAT_Y41B:
      sampling = GST_JPEG2000_SAMPLING_YBR411;
      break;
    case GST_VIDEO_FORMAT_I420_10LE:
    case GST_VIDEO_FORMAT_I420_10BE:
    case GST_VIDEO_FORMAT_I420:
      sampling = GST_JPEG2000_SAMPLING_YBR420;
      break;
    case GST_VIDEO_FORMAT_GRAY8:
    case GST_VIDEO_FORMAT_GRAY16_LE:
    case GST_VIDEO_FORMAT_GRAY16_BE:
      sampling = GST_JPEG2000_SAMPLING_GRAYSCALE;
      break;
    default:
      break;
  }

  if ((state->info.finfo->flags & GST_VIDEO_FORMAT_FLAG_YUV)) {
    colorspace = "sYUV";
  } else if ((state->info.finfo->flags & GST_VIDEO_FORMAT_FLAG_RGB)) {
    colorspace = "sRGB";
  } else if ((state->info.finfo->flags & GST_VIDEO_FORMAT_FLAG_GRAY)) {
    colorspace = "GRAY";
  } else
    g_return_val_if_reached (FALSE);

  if (stripe_mode) {
    caps = gst_caps_new_simple ("image/x-jpc-striped",
        "colorspace", G_TYPE_STRING, colorspace,
        "sampling", G_TYPE_STRING, gst_jpeg2000_sampling_to_string (sampling),
        "num-components", G_TYPE_INT, ncomps,
        "num-stripes", G_TYPE_INT, self->num_stripes,
        "stripe-height", G_TYPE_INT,
        get_stripe_height (self, 0,
            GST_VIDEO_INFO_COMP_HEIGHT (&state->info, 0)), NULL);
  } else if (sampling != GST_JPEG2000_SAMPLING_NONE) {
    caps = gst_caps_new_simple (gst_structure_get_name (s),
        "colorspace", G_TYPE_STRING, colorspace,
        "sampling", G_TYPE_STRING, gst_jpeg2000_sampling_to_string (sampling),
        "num-components", G_TYPE_INT, ncomps, NULL);
  } else {
    caps = gst_caps_new_simple (gst_structure_get_name (s),
        "colorspace", G_TYPE_STRING, colorspace,
        "num-components", G_TYPE_INT, ncomps, NULL);
  }
  gst_caps_unref (allowed_caps);

  if (self->output_state)
    gst_video_codec_state_unref (self->output_state);
  self->output_state =
      gst_video_encoder_set_output_state (encoder, caps, state);

  gst_video_encoder_negotiate (GST_VIDEO_ENCODER (encoder));

  return TRUE;
}

static opj_image_t *
gst_openjpeg_enc_fill_image (GstOpenJPEGEnc * self, GstVideoFrame * frame,
    guint slice_num)
{
  gint i, ncomps, temp, min_height = INT_MAX;
  opj_image_cmptparm_t *comps;
  OPJ_COLOR_SPACE colorspace;
  opj_image_t *image;

  ncomps = GST_VIDEO_FRAME_N_COMPONENTS (frame);
  comps = g_new0 (opj_image_cmptparm_t, ncomps);

  for (i = 0; i < ncomps; i++) {
    comps[i].prec = GST_VIDEO_FRAME_COMP_DEPTH (frame, i);
    comps[i].bpp = GST_VIDEO_FRAME_COMP_DEPTH (frame, i);
    comps[i].sgnd = 0;
    comps[i].w = GST_VIDEO_FRAME_COMP_WIDTH (frame, i);
    comps[i].dx =
        (guint) ((float) GST_VIDEO_FRAME_WIDTH (frame) /
        GST_VIDEO_FRAME_COMP_WIDTH (frame, i) + 0.5f);
    comps[i].dy =
        (guint) ((float) GST_VIDEO_FRAME_HEIGHT (frame) /
        GST_VIDEO_FRAME_COMP_HEIGHT (frame, i) + 0.5f);
    temp =
        (GST_VIDEO_FRAME_COMP_HEIGHT (frame,
            i) / self->num_stripes) * comps[i].dy;
    if (temp < min_height)
      min_height = temp;
  }

  for (i = 0; i < ncomps; i++) {
    gint nominal_height = min_height / comps[i].dy;

    comps[i].h = (slice_num < self->num_stripes) ?
        nominal_height
        : GST_VIDEO_FRAME_COMP_HEIGHT (frame,
        i) - (self->num_stripes - 1) * nominal_height;

  }

  if ((frame->info.finfo->flags & GST_VIDEO_FORMAT_FLAG_YUV))
    colorspace = OPJ_CLRSPC_SYCC;
  else if ((frame->info.finfo->flags & GST_VIDEO_FORMAT_FLAG_RGB))
    colorspace = OPJ_CLRSPC_SRGB;
  else if ((frame->info.finfo->flags & GST_VIDEO_FORMAT_FLAG_GRAY))
    colorspace = OPJ_CLRSPC_GRAY;
  else
    g_return_val_if_reached (NULL);

  image = opj_image_create (ncomps, comps, colorspace);
  if (!image) {
    GST_WARNING_OBJECT (self,
        "Unable to create a JPEG image. first component height=%d",
        ncomps ? comps[0].h : 0);
    return NULL;
  }

  g_free (comps);

  image->x0 = 0;
  image->x1 = GST_VIDEO_FRAME_WIDTH (frame);
  image->y0 = (slice_num - 1) * min_height;
  image->y1 =
      (slice_num <
      self->num_stripes) ? image->y0 +
      min_height : GST_VIDEO_FRAME_HEIGHT (frame);
  self->fill_image (image, frame);

  return image;
}

static void
gst_openjpeg_enc_opj_error (const char *msg, void *userdata)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (userdata);
  gchar *trimmed = g_strchomp (g_strdup (msg));
  GST_TRACE_OBJECT (self, "openjpeg error: %s", trimmed);
  g_free (trimmed);
}

static void
gst_openjpeg_enc_opj_warning (const char *msg, void *userdata)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (userdata);
  gchar *trimmed = g_strchomp (g_strdup (msg));
  GST_TRACE_OBJECT (self, "openjpeg warning: %s", trimmed);
  g_free (trimmed);
}

static void
gst_openjpeg_enc_opj_info (const char *msg, void *userdata)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (userdata);
  gchar *trimmed = g_strchomp (g_strdup (msg));
  GST_TRACE_OBJECT (self, "openjpeg info: %s", trimmed);
  g_free (trimmed);
}

typedef struct
{
  guint8 *data;
  guint allocsize;
  guint offset;
  guint size;
} MemStream;

static OPJ_SIZE_T
read_fn (void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
{
  g_return_val_if_reached (-1);
}

static OPJ_SIZE_T
write_fn (void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data)
{
  MemStream *mstream = p_user_data;

  if (mstream->offset + p_nb_bytes > mstream->allocsize) {
    while (mstream->offset + p_nb_bytes > mstream->allocsize)
      mstream->allocsize *= 2;
    mstream->data = g_realloc (mstream->data, mstream->allocsize);
  }

  memcpy (mstream->data + mstream->offset, p_buffer, p_nb_bytes);

  if (mstream->offset + p_nb_bytes > mstream->size)
    mstream->size = mstream->offset + p_nb_bytes;
  mstream->offset += p_nb_bytes;

  return p_nb_bytes;
}

static OPJ_OFF_T
skip_fn (OPJ_OFF_T p_nb_bytes, void *p_user_data)
{
  MemStream *mstream = p_user_data;

  if (mstream->offset + p_nb_bytes > mstream->allocsize) {
    while (mstream->offset + p_nb_bytes > mstream->allocsize)
      mstream->allocsize *= 2;
    mstream->data = g_realloc (mstream->data, mstream->allocsize);
  }

  if (mstream->offset + p_nb_bytes > mstream->size)
    mstream->size = mstream->offset + p_nb_bytes;

  mstream->offset += p_nb_bytes;

  return p_nb_bytes;
}

static OPJ_BOOL
seek_fn (OPJ_OFF_T p_nb_bytes, void *p_user_data)
{
  MemStream *mstream = p_user_data;

  if (p_nb_bytes > mstream->size)
    return OPJ_FALSE;

  mstream->offset = p_nb_bytes;

  return OPJ_TRUE;
}

static gboolean
gst_openjpeg_encode_is_last_subframe (GstVideoEncoder * enc, int stripe)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (enc);

  return (stripe == self->num_stripes);
}

static GstOpenJPEGCodecMessage *
gst_openjpeg_encode_message_new (GstOpenJPEGEnc * self,
    GstVideoCodecFrame * frame, int num_stripe)
{
  GstOpenJPEGCodecMessage *message = g_slice_new0 (GstOpenJPEGCodecMessage);

  message->frame = gst_video_codec_frame_ref (frame);
  message->stripe = num_stripe;
  message->last_error = OPENJPEG_ERROR_NONE;

  return message;
}

static GstOpenJPEGCodecMessage *
gst_openjpeg_encode_message_free (GstOpenJPEGCodecMessage * message)
{
  if (message) {
    gst_video_codec_frame_unref (message->frame);
    if (message->output_buffer)
      gst_buffer_unref (message->output_buffer);
    g_slice_free (GstOpenJPEGCodecMessage, message);
  }
  return NULL;
}

#define ENCODE_ERROR(encode_params, err_code) { \
      encode_params->last_error = err_code; \
      goto done; \
}

/* callback method to be called asynchronously or not*/
static void
gst_openjpeg_enc_encode_stripe (GstElement * element, gpointer user_data)
{
  GstOpenJPEGCodecMessage *message = (GstOpenJPEGCodecMessage *) user_data;
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (element);
  opj_codec_t *enc = NULL;
  opj_stream_t *stream = NULL;
  MemStream mstream;
  opj_image_t *image = NULL;
  GstVideoFrame vframe;

  GST_INFO_OBJECT (self, "Encode stripe %d/%d", message->stripe,
      self->num_stripes);

  mstream.data = NULL;
  enc = opj_create_compress (self->codec_format);
  if (!enc)
    ENCODE_ERROR (message, OPENJPEG_ERROR_INIT);

  if (G_UNLIKELY (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >=
          GST_LEVEL_TRACE)) {
    opj_set_info_handler (enc, gst_openjpeg_enc_opj_info, self);
    opj_set_warning_handler (enc, gst_openjpeg_enc_opj_warning, self);
    opj_set_error_handler (enc, gst_openjpeg_enc_opj_error, self);
  } else {
    opj_set_info_handler (enc, NULL, NULL);
    opj_set_warning_handler (enc, NULL, NULL);
    opj_set_error_handler (enc, NULL, NULL);
  }
  if (!gst_video_frame_map (&vframe, &self->input_state->info,
          message->frame->input_buffer, GST_MAP_READ))
    ENCODE_ERROR (message, OPENJPEG_ERROR_MAP_READ);
  image = gst_openjpeg_enc_fill_image (self, &vframe, message->stripe);
  gst_video_frame_unmap (&vframe);
  if (!image)
    ENCODE_ERROR (message, OPENJPEG_ERROR_FILL_IMAGE);

  if (vframe.info.finfo->flags & GST_VIDEO_FORMAT_FLAG_RGB) {
    self->params.tcp_mct = 1;
  }
  opj_setup_encoder (enc, &self->params, image);
  stream = opj_stream_create (4096, OPJ_FALSE);
  if (!stream)
    ENCODE_ERROR (message, OPENJPEG_ERROR_OPEN);

  mstream.allocsize = 4096;
  mstream.data = g_malloc (mstream.allocsize);
  mstream.offset = 0;
  mstream.size = 0;

  opj_stream_set_read_function (stream, read_fn);
  opj_stream_set_write_function (stream, write_fn);
  opj_stream_set_skip_function (stream, skip_fn);
  opj_stream_set_seek_function (stream, seek_fn);
  opj_stream_set_user_data (stream, &mstream, NULL);
  opj_stream_set_user_data_length (stream, mstream.size);

  if (!opj_start_compress (enc, image, stream))
    ENCODE_ERROR (message, OPENJPEG_ERROR_ENCODE);

  if (!opj_encode (enc, stream))
    ENCODE_ERROR (message, OPENJPEG_ERROR_ENCODE);

  if (!opj_end_compress (enc, stream))
    ENCODE_ERROR (message, OPENJPEG_ERROR_ENCODE);

  opj_image_destroy (image);
  image = NULL;
  opj_stream_destroy (stream);
  stream = NULL;
  opj_destroy_codec (enc);
  enc = NULL;

  message->output_buffer = gst_buffer_new ();

  if (self->is_jp2c) {
    GstMapInfo map;
    GstMemory *mem;

    mem = gst_allocator_alloc (NULL, 8, NULL);
    gst_memory_map (mem, &map, GST_MAP_WRITE);
    GST_WRITE_UINT32_BE (map.data, mstream.size + 8);
    GST_WRITE_UINT32_BE (map.data + 4, GST_MAKE_FOURCC ('j', 'p', '2', 'c'));
    gst_memory_unmap (mem, &map);
    gst_buffer_append_memory (message->output_buffer, mem);
  }

  gst_buffer_append_memory (message->output_buffer,
      gst_memory_new_wrapped (0, mstream.data, mstream.allocsize, 0,
          mstream.size, mstream.data, (GDestroyNotify) g_free));
  message->last_error = OPENJPEG_ERROR_NONE;

  GST_INFO_OBJECT (self,
      "Stripe %d encoded successfully, pass it to the streaming thread",
      message->stripe);

done:
  if (message->last_error != OPENJPEG_ERROR_NONE) {
    if (mstream.data)
      g_free (mstream.data);
    if (enc)
      opj_destroy_codec (enc);
    if (image)
      opj_image_destroy (image);
    if (stream)
      opj_stream_destroy (stream);
  }
  if (!message->direct) {
    GST_OBJECT_LOCK (self);
    g_queue_push_tail (&self->messages, message);
    g_cond_signal (&self->messages_cond);
    GST_OBJECT_UNLOCK (self);
  }

}

static GstOpenJPEGCodecMessage *
gst_openjpeg_enc_wait_for_new_message (GstOpenJPEGEnc * self)
{
  GstOpenJPEGCodecMessage *message = NULL;

  GST_OBJECT_LOCK (self);
  while (g_queue_is_empty (&self->messages))
    g_cond_wait (&self->messages_cond, GST_OBJECT_GET_LOCK (self));
  message = g_queue_pop_head (&self->messages);
  GST_OBJECT_UNLOCK (self);

  return message;
}

static GstFlowReturn
gst_openjpeg_enc_encode_frame_multiple (GstVideoEncoder * encoder,
    GstVideoCodecFrame * frame)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (encoder);
  GstFlowReturn ret = GST_FLOW_OK;
  guint i;
  guint encoded_stripes = 0;
  guint enqueued_stripes = 0;
  GstOpenJPEGCodecMessage *message = NULL;

  /* The method receives a frame and split it into n stripes and
   * and create a thread per stripe to encode it.
   * As the number of stripes can be greater than the
   * available threads to encode, there is two loop, one to
   * count the enqueues stripes and one to count the encoded
   * stripes.
   */
  while (encoded_stripes < self->num_stripes) {
    for (i = 1;
        i <= self->available_threads
        && enqueued_stripes < (self->num_stripes - encoded_stripes); i++) {
      message =
          gst_openjpeg_encode_message_new (self, frame, i + encoded_stripes);
      GST_LOG_OBJECT (self,
          "About to enqueue an encoding message from frame %p stripe %d", frame,
          message->stripe);
      gst_element_call_async (GST_ELEMENT (self),
          (GstElementCallAsyncFunc) gst_openjpeg_enc_encode_stripe, message,
          NULL);
      enqueued_stripes++;
    }
    while (enqueued_stripes > 0) {
      message = gst_openjpeg_enc_wait_for_new_message (self);
      if (!message)
        continue;
      enqueued_stripes--;
      if (message->last_error == OPENJPEG_ERROR_NONE) {
        GST_LOG_OBJECT (self,
            "About to push frame %p stripe %d", frame, message->stripe);
        frame->output_buffer = gst_buffer_ref (message->output_buffer);
        if (gst_openjpeg_encode_is_last_subframe (encoder, encoded_stripes + 1)) {
          GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
          ret = gst_video_encoder_finish_frame (encoder, frame);
        } else
          ret = gst_video_encoder_finish_subframe (encoder, frame);
        if (ret != GST_FLOW_OK) {
          GST_WARNING_OBJECT
              (self, "An error occurred pushing the frame %s",
              gst_flow_get_name (ret));
          goto done;
        }
        encoded_stripes++;
        message = gst_openjpeg_encode_message_free (message);
      } else {
        GST_WARNING_OBJECT
            (self, "An error occurred %d during the JPEG encoding",
            message->last_error);
        gst_video_codec_frame_unref (frame);
        self->last_error = message->last_error;
        ret = GST_FLOW_ERROR;
        goto done;
      }
    }
  }

done:
  gst_openjpeg_encode_message_free (message);
  return ret;
}

static GstFlowReturn
gst_openjpeg_enc_encode_frame_single (GstVideoEncoder * encoder,
    GstVideoCodecFrame * frame)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (encoder);
  GstFlowReturn ret = GST_FLOW_OK;
  guint i;
  GstOpenJPEGCodecMessage *message = NULL;

  for (i = 1; i <= self->num_stripes; ++i) {
    message = gst_openjpeg_encode_message_new (self, frame, i);
    message->direct = TRUE;
    gst_openjpeg_enc_encode_stripe (GST_ELEMENT (self), message);
    if (message->last_error != OPENJPEG_ERROR_NONE) {
      GST_WARNING_OBJECT
          (self, "An error occured %d during the JPEG encoding",
          message->last_error);
      gst_video_codec_frame_unref (frame);
      self->last_error = message->last_error;
      ret = GST_FLOW_ERROR;
      goto done;
    }
    frame->output_buffer = gst_buffer_ref (message->output_buffer);
    if (gst_openjpeg_encode_is_last_subframe (encoder, message->stripe)) {
      GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
      ret = gst_video_encoder_finish_frame (encoder, frame);
    } else
      ret = gst_video_encoder_finish_subframe (encoder, frame);
    if (ret != GST_FLOW_OK) {
      GST_WARNING_OBJECT
          (self, "An error occurred pushing the frame %s",
          gst_flow_get_name (ret));
      goto done;
    }
    message = gst_openjpeg_encode_message_free (message);
  }

done:
  gst_openjpeg_encode_message_free (message);
  return ret;
}

static GstFlowReturn
gst_openjpeg_enc_handle_frame (GstVideoEncoder * encoder,
    GstVideoCodecFrame * frame)
{
  GstOpenJPEGEnc *self = GST_OPENJPEG_ENC (encoder);
  GstFlowReturn ret = GST_FLOW_OK;
  GstVideoFrame vframe;
  gboolean subframe_mode =
      self->num_stripes != GST_OPENJPEG_ENC_DEFAULT_NUM_STRIPES;

  GST_DEBUG_OBJECT (self, "Handling frame");

  if (subframe_mode) {
    gint min_res;

    /* due to limitations in openjpeg library,
     * number of wavelet resolutions must not exceed floor(log(stripe height)) + 1 */
    if (!gst_video_frame_map (&vframe, &self->input_state->info,
            frame->input_buffer, GST_MAP_READ)) {
      gst_video_codec_frame_unref (frame);
      self->last_error = OPENJPEG_ERROR_MAP_READ;
      goto error;
    }
    /* find stripe with least height */
    min_res =
        get_stripe_height (self, self->num_stripes - 1,
        GST_VIDEO_FRAME_COMP_HEIGHT (&vframe, 0));
    min_res = MIN (min_res, get_stripe_height (self, 0,
            GST_VIDEO_FRAME_COMP_HEIGHT (&vframe, 0)));
    /* take log to find correct number of wavelet resolutions */
    min_res = min_res > 1 ? (gint) log (min_res) + 1 : 1;
    self->params.numresolution = MIN (min_res + 1, self->params.numresolution);
    gst_video_frame_unmap (&vframe);
  }
  if (self->encode_frame (encoder, frame) != GST_FLOW_OK)
    goto error;

  return ret;

error:
  switch (self->last_error) {
    case OPENJPEG_ERROR_INIT:
      GST_ELEMENT_ERROR (self, LIBRARY, INIT,
          ("Failed to initialize OpenJPEG encoder"), (NULL));
      break;
    case OPENJPEG_ERROR_MAP_READ:
      GST_ELEMENT_ERROR (self, CORE, FAILED,
          ("Failed to map input buffer"), (NULL));
      break;
    case OPENJPEG_ERROR_FILL_IMAGE:
      GST_ELEMENT_ERROR (self, LIBRARY, INIT,
          ("Failed to fill OpenJPEG image"), (NULL));
      break;
    case OPENJPEG_ERROR_OPEN:
      GST_ELEMENT_ERROR (self, LIBRARY, INIT,
          ("Failed to open OpenJPEG data"), (NULL));
      break;
    case OPENJPEG_ERROR_ENCODE:
      GST_ELEMENT_ERROR (self, LIBRARY, INIT,
          ("Failed to encode OpenJPEG data"), (NULL));
      break;
    default:
      GST_ELEMENT_ERROR (self, LIBRARY, INIT,
          ("Failed to encode OpenJPEG data"), (NULL));
      break;
  }
  gst_openjpeg_enc_flush_messages (self);
  return GST_FLOW_ERROR;
}

static gboolean
gst_openjpeg_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);
}
