/* GStreamer
 * Copyright (C) 2010 David Schleef <ds@schleef.org>
 * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
 *
 * 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

#if 0
#ifdef HAVE_PTHREAD
#define _GNU_SOURCE
#include <pthread.h>
#endif
#endif

#include "video-converter.h"

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

#include "video-orc.h"

/**
 * SECTION:videoconverter
 * @title: GstVideoConverter
 * @short_description: Generic video conversion
 *
 * This object is used to convert video frames from one format to another.
 * The object can perform conversion of:
 *
 *  * video format
 *  * video colorspace
 *  * chroma-siting
 *  * video size
 *
 */

/*
 * (a)  unpack
 * (b)  chroma upsample
 * (c)  (convert Y'CbCr to R'G'B')
 * (d)  gamma decode
 * (e)  downscale
 * (f)  colorspace convert through XYZ
 * (g)  upscale
 * (h)  gamma encode
 * (i)  (convert R'G'B' to Y'CbCr)
 * (j)  chroma downsample
 * (k)  pack
 *
 * quality options
 *
 *  (a) range truncate, range expand
 *  (b) full upsample, 1-1 non-cosited upsample, no upsample
 *  (c) 8 bits, 16 bits
 *  (d)
 *  (e) 8 bits, 16 bits
 *  (f) 8 bits, 16 bits
 *  (g) 8 bits, 16 bits
 *  (h)
 *  (i) 8 bits, 16 bits
 *  (j) 1-1 cosited downsample, no downsample
 *  (k)
 *
 *
 *         1 : a ->   ->   ->   -> e  -> f  -> g  ->   ->   ->   -> k
 *         2 : a ->   ->   ->   -> e  -> f* -> g  ->   ->   ->   -> k
 *         3 : a ->   ->   ->   -> e* -> f* -> g* ->   ->   ->   -> k
 *         4 : a -> b ->   ->   -> e  -> f  -> g  ->   ->   -> j -> k
 *         5 : a -> b ->   ->   -> e* -> f* -> g* ->   ->   -> j -> k
 *         6 : a -> b -> c -> d -> e  -> f  -> g  -> h -> i -> j -> k
 *         7 : a -> b -> c -> d -> e* -> f* -> g* -> h -> i -> j -> k
 *
 *         8 : a -> b -> c -> d -> e* -> f* -> g* -> h -> i -> j -> k
 *         9 : a -> b -> c -> d -> e* -> f* -> g* -> h -> i -> j -> k
 *        10 : a -> b -> c -> d -> e* -> f* -> g* -> h -> i -> j -> k
 */

#ifndef GST_DISABLE_GST_DEBUG
#define GST_CAT_DEFAULT ensure_debug_category()
static GstDebugCategory *
ensure_debug_category (void)
{
  static gsize cat_gonce = 0;

  if (g_once_init_enter (&cat_gonce)) {
    gsize cat_done;

    cat_done = (gsize) _gst_debug_category_new ("video-converter", 0,
        "video-converter object");

    g_once_init_leave (&cat_gonce, cat_done);
  }

  return (GstDebugCategory *) cat_gonce;
}
#else
#define ensure_debug_category() /* NOOP */
#endif /* GST_DISABLE_GST_DEBUG */

typedef void (*GstParallelizedTaskFunc) (gpointer user_data);

typedef struct _GstParallelizedTaskRunner GstParallelizedTaskRunner;
typedef struct _GstParallelizedTaskThread GstParallelizedTaskThread;

struct _GstParallelizedTaskThread
{
  GstParallelizedTaskRunner *runner;
  guint idx;
  GThread *thread;
};

struct _GstParallelizedTaskRunner
{
  guint n_threads;

  GstParallelizedTaskThread *threads;

  GstParallelizedTaskFunc func;
  gpointer *task_data;

  GMutex lock;
  GCond cond_todo, cond_done;
  gint n_todo, n_done;
  gboolean quit;
};

static gpointer
gst_parallelized_task_thread_func (gpointer data)
{
  GstParallelizedTaskThread *self = data;

#if 0
#ifdef HAVE_PTHREAD
  {
    pthread_t thread = pthread_self ();
    cpu_set_t cpuset;
    int r;

    CPU_ZERO (&cpuset);
    CPU_SET (self->idx, &cpuset);
    if ((r = pthread_setaffinity_np (thread, sizeof (cpuset), &cpuset)) != 0)
      GST_ERROR ("Failed to set thread affinity for thread %d: %s", self->idx,
          g_strerror (r));
  }
#endif
#endif

  g_mutex_lock (&self->runner->lock);
  self->runner->n_done++;
  if (self->runner->n_done == self->runner->n_threads - 1)
    g_cond_signal (&self->runner->cond_done);

  do {
    gint idx;

    while (self->runner->n_todo == -1 && !self->runner->quit)
      g_cond_wait (&self->runner->cond_todo, &self->runner->lock);

    if (self->runner->quit)
      break;

    idx = self->runner->n_todo--;
    g_assert (self->runner->n_todo >= -1);
    g_mutex_unlock (&self->runner->lock);

    g_assert (self->runner->func != NULL);

    self->runner->func (self->runner->task_data[idx]);

    g_mutex_lock (&self->runner->lock);
    self->runner->n_done++;
    if (self->runner->n_done == self->runner->n_threads - 1)
      g_cond_signal (&self->runner->cond_done);
  } while (TRUE);

  g_mutex_unlock (&self->runner->lock);

  return NULL;
}

static void
gst_parallelized_task_runner_free (GstParallelizedTaskRunner * self)
{
  guint i;

  g_mutex_lock (&self->lock);
  self->quit = TRUE;
  g_cond_broadcast (&self->cond_todo);
  g_mutex_unlock (&self->lock);

  for (i = 1; i < self->n_threads; i++) {
    if (!self->threads[i].thread)
      continue;

    g_thread_join (self->threads[i].thread);
  }

  g_mutex_clear (&self->lock);
  g_cond_clear (&self->cond_todo);
  g_cond_clear (&self->cond_done);
  g_free (self->threads);
  g_free (self);
}

static GstParallelizedTaskRunner *
gst_parallelized_task_runner_new (guint n_threads)
{
  GstParallelizedTaskRunner *self;
  guint i;
  GError *err = NULL;

  if (n_threads == 0)
    n_threads = g_get_num_processors ();

  self = g_new0 (GstParallelizedTaskRunner, 1);
  self->n_threads = n_threads;
  self->threads = g_new0 (GstParallelizedTaskThread, n_threads);

  self->quit = FALSE;
  self->n_todo = -1;
  self->n_done = 0;
  g_mutex_init (&self->lock);
  g_cond_init (&self->cond_todo);
  g_cond_init (&self->cond_done);

  /* Set when scheduling a job */
  self->func = NULL;
  self->task_data = NULL;

  for (i = 0; i < n_threads; i++) {
    self->threads[i].runner = self;
    self->threads[i].idx = i;

    /* First thread is the one calling run() */
    if (i > 0) {
      self->threads[i].thread =
          g_thread_try_new ("videoconvert", gst_parallelized_task_thread_func,
          &self->threads[i], &err);
      if (!self->threads[i].thread)
        goto error;
    }
  }

  g_mutex_lock (&self->lock);
  while (self->n_done < self->n_threads - 1)
    g_cond_wait (&self->cond_done, &self->lock);
  self->n_done = 0;
  g_mutex_unlock (&self->lock);

  return self;

error:
  {
    GST_ERROR ("Failed to start thread %u: %s", i, err->message);
    g_clear_error (&err);

    gst_parallelized_task_runner_free (self);
    return NULL;
  }
}

static void
gst_parallelized_task_runner_run (GstParallelizedTaskRunner * self,
    GstParallelizedTaskFunc func, gpointer * task_data)
{
  guint n_threads = self->n_threads;

  self->func = func;
  self->task_data = task_data;

  if (n_threads > 1) {
    g_mutex_lock (&self->lock);
    self->n_todo = self->n_threads - 2;
    self->n_done = 0;
    g_cond_broadcast (&self->cond_todo);
    g_mutex_unlock (&self->lock);
  }

  self->func (self->task_data[self->n_threads - 1]);

  if (n_threads > 1) {
    g_mutex_lock (&self->lock);
    while (self->n_done < self->n_threads - 1)
      g_cond_wait (&self->cond_done, &self->lock);
    self->n_done = 0;
    g_mutex_unlock (&self->lock);
  }

  self->func = NULL;
  self->task_data = NULL;
}

typedef struct _GstLineCache GstLineCache;

#define SCALE    (8)
#define SCALE_F  ((float) (1 << SCALE))

typedef struct _MatrixData MatrixData;

struct _MatrixData
{
  gdouble dm[4][4];
  gint im[4][4];
  gint width;
  guint64 orc_p1;
  guint64 orc_p2;
  guint64 orc_p3;
  guint64 orc_p4;
  gint64 *t_r;
  gint64 *t_g;
  gint64 *t_b;
  gint64 t_c;
  void (*matrix_func) (MatrixData * data, gpointer pixels);
};

typedef struct _GammaData GammaData;

struct _GammaData
{
  gpointer gamma_table;
  gint width;
  void (*gamma_func) (GammaData * data, gpointer dest, gpointer src);
};

typedef enum
{
  ALPHA_MODE_NONE = 0,
  ALPHA_MODE_COPY = (1 << 0),
  ALPHA_MODE_SET = (1 << 1),
  ALPHA_MODE_MULT = (1 << 2)
} AlphaMode;

typedef struct
{
  guint8 *data;
  guint stride;
  guint n_lines;
  guint idx;
  gpointer user_data;
  GDestroyNotify notify;
} ConverterAlloc;

typedef void (*FastConvertFunc) (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane);

struct _GstVideoConverter
{
  gint flags;

  GstVideoInfo in_info;
  GstVideoInfo out_info;

  gint in_x;
  gint in_y;
  gint in_width;
  gint in_height;
  gint in_maxwidth;
  gint in_maxheight;
  gint out_x;
  gint out_y;
  gint out_width;
  gint out_height;
  gint out_maxwidth;
  gint out_maxheight;

  gint current_pstride;
  gint current_width;
  gint current_height;
  GstVideoFormat current_format;
  gint current_bits;

  GstStructure *config;

  GstParallelizedTaskRunner *conversion_runner;

  guint16 **tmpline;

  gboolean fill_border;
  gpointer borderline;
  guint64 borders[4];
  guint32 border_argb;
  guint32 alpha_value;
  AlphaMode alpha_mode;

  void (*convert) (GstVideoConverter * convert, const GstVideoFrame * src,
      GstVideoFrame * dest);

  /* data for unpack */
  GstLineCache **unpack_lines;
  GstVideoFormat unpack_format;
  guint unpack_bits;
  gboolean unpack_rgb;
  gboolean identity_unpack;
  gint unpack_pstride;

  /* chroma upsample */
  GstLineCache **upsample_lines;
  GstVideoChromaResample **upsample;
  GstVideoChromaResample **upsample_p;
  GstVideoChromaResample **upsample_i;
  guint up_n_lines;
  gint up_offset;

  /* to R'G'B */
  GstLineCache **to_RGB_lines;
  MatrixData to_RGB_matrix;
  /* gamma decode */
  GammaData gamma_dec;

  /* scaling */
  GstLineCache **hscale_lines;
  GstVideoScaler **h_scaler;
  gint h_scale_format;
  GstLineCache **vscale_lines;
  GstVideoScaler **v_scaler;
  GstVideoScaler **v_scaler_p;
  GstVideoScaler **v_scaler_i;
  gint v_scale_width;
  gint v_scale_format;

  /* color space conversion */
  GstLineCache **convert_lines;
  MatrixData convert_matrix;
  gint in_bits;
  gint out_bits;

  /* alpha correction */
  GstLineCache **alpha_lines;
  void (*alpha_func) (GstVideoConverter * convert, gpointer pixels, gint width);

  /* gamma encode */
  GammaData gamma_enc;
  /* to Y'CbCr */
  GstLineCache **to_YUV_lines;
  MatrixData to_YUV_matrix;

  /* chroma downsample */
  GstLineCache **downsample_lines;
  GstVideoChromaResample **downsample;
  GstVideoChromaResample **downsample_p;
  GstVideoChromaResample **downsample_i;
  guint down_n_lines;
  gint down_offset;

  /* dither */
  GstLineCache **dither_lines;
  GstVideoDither **dither;

  /* pack */
  GstLineCache **pack_lines;
  guint pack_nlines;
  GstVideoFormat pack_format;
  guint pack_bits;
  gboolean pack_rgb;
  gboolean identity_pack;
  gint pack_pstride;
  gconstpointer pack_pal;
  gsize pack_palsize;

  const GstVideoFrame *src;
  GstVideoFrame *dest;

  /* fastpath */
  GstVideoFormat fformat[4];
  gint fin_x[4];
  gint fin_y[4];
  gint fout_x[4];
  gint fout_y[4];
  gint fout_width[4];
  gint fout_height[4];
  gint fsplane[4];
  gint ffill[4];

  struct
  {
    GstVideoScaler **scaler;
  } fh_scaler[4];
  struct
  {
    GstVideoScaler **scaler;
  } fv_scaler[4];
  FastConvertFunc fconvert[4];
};

typedef gpointer (*GstLineCacheAllocLineFunc) (GstLineCache * cache, gint idx,
    gpointer user_data);
typedef gboolean (*GstLineCacheNeedLineFunc) (GstLineCache * cache, gint idx,
    gint out_line, gint in_line, gpointer user_data);

struct _GstLineCache
{
  gint first;
  gint backlog;
  GPtrArray *lines;

  GstLineCache *prev;
  gboolean write_input;
  gboolean pass_alloc;
  gboolean alloc_writable;

  GstLineCacheNeedLineFunc need_line;
  gint need_line_idx;
  gpointer need_line_data;
  GDestroyNotify need_line_notify;

  guint n_lines;
  guint stride;
  GstLineCacheAllocLineFunc alloc_line;
  gpointer alloc_line_data;
  GDestroyNotify alloc_line_notify;
};

static GstLineCache *
gst_line_cache_new (GstLineCache * prev)
{
  GstLineCache *result;

  result = g_slice_new0 (GstLineCache);
  result->lines = g_ptr_array_new ();
  result->prev = prev;

  return result;
}

static void
gst_line_cache_clear (GstLineCache * cache)
{
  g_return_if_fail (cache != NULL);

  g_ptr_array_set_size (cache->lines, 0);
  cache->first = 0;
}

static void
gst_line_cache_free (GstLineCache * cache)
{
  if (cache->need_line_notify)
    cache->need_line_notify (cache->need_line_data);
  if (cache->alloc_line_notify)
    cache->alloc_line_notify (cache->alloc_line_data);
  gst_line_cache_clear (cache);
  g_ptr_array_unref (cache->lines);
  g_slice_free (GstLineCache, cache);
}

static void
gst_line_cache_set_need_line_func (GstLineCache * cache,
    GstLineCacheNeedLineFunc need_line, gint idx, gpointer user_data,
    GDestroyNotify notify)
{
  cache->need_line = need_line;
  cache->need_line_idx = idx;
  cache->need_line_data = user_data;
  cache->need_line_notify = notify;
}

static void
gst_line_cache_set_alloc_line_func (GstLineCache * cache,
    GstLineCacheAllocLineFunc alloc_line, gpointer user_data,
    GDestroyNotify notify)
{
  cache->alloc_line = alloc_line;
  cache->alloc_line_data = user_data;
  cache->alloc_line_notify = notify;
}

/* keep this much backlog for interlaced video */
#define BACKLOG 2

static gpointer *
gst_line_cache_get_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gint n_lines)
{
  if (cache->first + cache->backlog < in_line) {
    gint to_remove =
        MIN (in_line - (cache->first + cache->backlog), cache->lines->len);
    if (to_remove > 0) {
      g_ptr_array_remove_range (cache->lines, 0, to_remove);
    }
    cache->first += to_remove;
  } else if (in_line < cache->first) {
    gst_line_cache_clear (cache);
    cache->first = in_line;
  }

  while (TRUE) {
    gint oline;

    if (cache->first <= in_line
        && in_line + n_lines <= cache->first + (gint) cache->lines->len) {
      return cache->lines->pdata + (in_line - cache->first);
    }

    if (cache->need_line == NULL)
      break;

    oline = out_line + cache->first + cache->lines->len - in_line;

    if (!cache->need_line (cache, idx, oline, cache->first + cache->lines->len,
            cache->need_line_data))
      break;
  }
  GST_DEBUG ("no lines");
  return NULL;
}

static void
gst_line_cache_add_line (GstLineCache * cache, gint idx, gpointer line)
{
  if (cache->first + cache->lines->len != idx) {
    gst_line_cache_clear (cache);
    cache->first = idx;
  }
  g_ptr_array_add (cache->lines, line);
}

static gpointer
gst_line_cache_alloc_line (GstLineCache * cache, gint idx)
{
  gpointer res;

  if (cache->alloc_line)
    res = cache->alloc_line (cache, idx, cache->alloc_line_data);
  else
    res = NULL;

  return res;
}

static void video_converter_generic (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest);
static gboolean video_converter_lookup_fastpath (GstVideoConverter * convert);
static void video_converter_compute_matrix (GstVideoConverter * convert);
static void video_converter_compute_resample (GstVideoConverter * convert,
    gint idx);

static gpointer get_dest_line (GstLineCache * cache, gint idx,
    gpointer user_data);

static gboolean do_unpack_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data);
static gboolean do_downsample_lines (GstLineCache * cache, gint idx,
    gint out_line, gint in_line, gpointer user_data);
static gboolean do_convert_to_RGB_lines (GstLineCache * cache, gint idx,
    gint out_line, gint in_line, gpointer user_data);
static gboolean do_convert_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data);
static gboolean do_alpha_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data);
static gboolean do_convert_to_YUV_lines (GstLineCache * cache, gint idx,
    gint out_line, gint in_line, gpointer user_data);
static gboolean do_upsample_lines (GstLineCache * cache, gint idx,
    gint out_line, gint in_line, gpointer user_data);
static gboolean do_vscale_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data);
static gboolean do_hscale_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data);
static gboolean do_dither_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data);

static ConverterAlloc *
converter_alloc_new (guint stride, guint n_lines, gpointer user_data,
    GDestroyNotify notify)
{
  ConverterAlloc *alloc;

  GST_DEBUG ("stride %d, n_lines %d", stride, n_lines);
  alloc = g_slice_new0 (ConverterAlloc);
  alloc->data = g_malloc (stride * n_lines);
  alloc->stride = stride;
  alloc->n_lines = n_lines;
  alloc->idx = 0;
  alloc->user_data = user_data;
  alloc->notify = notify;

  return alloc;
}

static void
converter_alloc_free (ConverterAlloc * alloc)
{
  if (alloc->notify)
    alloc->notify (alloc->user_data);
  g_free (alloc->data);
  g_slice_free (ConverterAlloc, alloc);
}

static void
setup_border_alloc (GstVideoConverter * convert, ConverterAlloc * alloc)
{
  gint i;

  if (convert->borderline) {
    for (i = 0; i < alloc->n_lines; i++)
      memcpy (&alloc->data[i * alloc->stride], convert->borderline,
          alloc->stride);
  }
}

static gpointer
get_temp_line (GstLineCache * cache, gint idx, gpointer user_data)
{
  ConverterAlloc *alloc = user_data;
  gpointer tmpline;

  GST_DEBUG ("get temp line %d (%p %d)", idx, alloc, alloc->idx);
  tmpline = &alloc->data[alloc->stride * alloc->idx];
  alloc->idx = (alloc->idx + 1) % alloc->n_lines;

  return tmpline;
}

static gpointer
get_border_temp_line (GstLineCache * cache, gint idx, gpointer user_data)
{
  ConverterAlloc *alloc = user_data;
  GstVideoConverter *convert = alloc->user_data;
  gpointer tmpline;

  GST_DEBUG ("get temp line %d (%p %d)", idx, alloc, alloc->idx);
  tmpline = &alloc->data[alloc->stride * alloc->idx] +
      (convert->out_x * convert->pack_pstride);
  alloc->idx = (alloc->idx + 1) % alloc->n_lines;

  return tmpline;
}

static gint
get_opt_int (GstVideoConverter * convert, const gchar * opt, gint def)
{
  gint res;
  if (!gst_structure_get_int (convert->config, opt, &res))
    res = def;
  return res;
}

static guint
get_opt_uint (GstVideoConverter * convert, const gchar * opt, guint def)
{
  guint res;
  if (!gst_structure_get_uint (convert->config, opt, &res))
    res = def;
  return res;
}

static gdouble
get_opt_double (GstVideoConverter * convert, const gchar * opt, gdouble def)
{
  gdouble res;
  if (!gst_structure_get_double (convert->config, opt, &res))
    res = def;
  return res;
}

static gboolean
get_opt_bool (GstVideoConverter * convert, const gchar * opt, gboolean def)
{
  gboolean res;
  if (!gst_structure_get_boolean (convert->config, opt, &res))
    res = def;
  return res;
}

static gint
get_opt_enum (GstVideoConverter * convert, const gchar * opt, GType type,
    gint def)
{
  gint res;
  if (!gst_structure_get_enum (convert->config, opt, type, &res))
    res = def;
  return res;
}

#define DEFAULT_OPT_FILL_BORDER TRUE
#define DEFAULT_OPT_ALPHA_VALUE 1.0
/* options copy, set, mult */
#define DEFAULT_OPT_ALPHA_MODE GST_VIDEO_ALPHA_MODE_COPY
#define DEFAULT_OPT_BORDER_ARGB 0xff000000
/* options full, input-only, output-only, none */
#define DEFAULT_OPT_MATRIX_MODE GST_VIDEO_MATRIX_MODE_FULL
/* none, remap */
#define DEFAULT_OPT_GAMMA_MODE GST_VIDEO_GAMMA_MODE_NONE
/* none, merge-only, fast */
#define DEFAULT_OPT_PRIMARIES_MODE GST_VIDEO_PRIMARIES_MODE_NONE
/* options full, upsample-only, downsample-only, none */
#define DEFAULT_OPT_CHROMA_MODE GST_VIDEO_CHROMA_MODE_FULL
#define DEFAULT_OPT_RESAMPLER_METHOD GST_VIDEO_RESAMPLER_METHOD_CUBIC
#define DEFAULT_OPT_CHROMA_RESAMPLER_METHOD GST_VIDEO_RESAMPLER_METHOD_LINEAR
#define DEFAULT_OPT_RESAMPLER_TAPS 0
#define DEFAULT_OPT_DITHER_METHOD GST_VIDEO_DITHER_BAYER
#define DEFAULT_OPT_DITHER_QUANTIZATION 1

#define GET_OPT_FILL_BORDER(c) get_opt_bool(c, \
    GST_VIDEO_CONVERTER_OPT_FILL_BORDER, DEFAULT_OPT_FILL_BORDER)
#define GET_OPT_ALPHA_VALUE(c) get_opt_double(c, \
    GST_VIDEO_CONVERTER_OPT_ALPHA_VALUE, DEFAULT_OPT_ALPHA_VALUE)
#define GET_OPT_ALPHA_MODE(c) get_opt_enum(c, \
    GST_VIDEO_CONVERTER_OPT_ALPHA_MODE, GST_TYPE_VIDEO_ALPHA_MODE, DEFAULT_OPT_ALPHA_MODE)
#define GET_OPT_BORDER_ARGB(c) get_opt_uint(c, \
    GST_VIDEO_CONVERTER_OPT_BORDER_ARGB, DEFAULT_OPT_BORDER_ARGB)
#define GET_OPT_MATRIX_MODE(c) get_opt_enum(c, \
    GST_VIDEO_CONVERTER_OPT_MATRIX_MODE, GST_TYPE_VIDEO_MATRIX_MODE, DEFAULT_OPT_MATRIX_MODE)
#define GET_OPT_GAMMA_MODE(c) get_opt_enum(c, \
    GST_VIDEO_CONVERTER_OPT_GAMMA_MODE, GST_TYPE_VIDEO_GAMMA_MODE, DEFAULT_OPT_GAMMA_MODE)
#define GET_OPT_PRIMARIES_MODE(c) get_opt_enum(c, \
    GST_VIDEO_CONVERTER_OPT_PRIMARIES_MODE, GST_TYPE_VIDEO_PRIMARIES_MODE, DEFAULT_OPT_PRIMARIES_MODE)
#define GET_OPT_CHROMA_MODE(c) get_opt_enum(c, \
    GST_VIDEO_CONVERTER_OPT_CHROMA_MODE, GST_TYPE_VIDEO_CHROMA_MODE, DEFAULT_OPT_CHROMA_MODE)
#define GET_OPT_RESAMPLER_METHOD(c) get_opt_enum(c, \
    GST_VIDEO_CONVERTER_OPT_RESAMPLER_METHOD, GST_TYPE_VIDEO_RESAMPLER_METHOD, \
    DEFAULT_OPT_RESAMPLER_METHOD)
#define GET_OPT_CHROMA_RESAMPLER_METHOD(c) get_opt_enum(c, \
    GST_VIDEO_CONVERTER_OPT_CHROMA_RESAMPLER_METHOD, GST_TYPE_VIDEO_RESAMPLER_METHOD, \
    DEFAULT_OPT_CHROMA_RESAMPLER_METHOD)
#define GET_OPT_RESAMPLER_TAPS(c) get_opt_uint(c, \
    GST_VIDEO_CONVERTER_OPT_RESAMPLER_TAPS, DEFAULT_OPT_RESAMPLER_TAPS)
#define GET_OPT_DITHER_METHOD(c) get_opt_enum(c, \
    GST_VIDEO_CONVERTER_OPT_DITHER_METHOD, GST_TYPE_VIDEO_DITHER_METHOD, \
    DEFAULT_OPT_DITHER_METHOD)
#define GET_OPT_DITHER_QUANTIZATION(c) get_opt_uint(c, \
    GST_VIDEO_CONVERTER_OPT_DITHER_QUANTIZATION, DEFAULT_OPT_DITHER_QUANTIZATION)

#define CHECK_ALPHA_COPY(c) (GET_OPT_ALPHA_MODE(c) == GST_VIDEO_ALPHA_MODE_COPY)
#define CHECK_ALPHA_SET(c) (GET_OPT_ALPHA_MODE(c) == GST_VIDEO_ALPHA_MODE_SET)
#define CHECK_ALPHA_MULT(c) (GET_OPT_ALPHA_MODE(c) == GST_VIDEO_ALPHA_MODE_MULT)

#define CHECK_MATRIX_FULL(c) (GET_OPT_MATRIX_MODE(c) == GST_VIDEO_MATRIX_MODE_FULL)
#define CHECK_MATRIX_INPUT(c) (GET_OPT_MATRIX_MODE(c) == GST_VIDEO_MATRIX_MODE_INPUT_ONLY)
#define CHECK_MATRIX_OUTPUT(c) (GET_OPT_MATRIX_MODE(c) == GST_VIDEO_MATRIX_MODE_OUTPUT_ONLY)
#define CHECK_MATRIX_NONE(c) (GET_OPT_MATRIX_MODE(c) == GST_VIDEO_MATRIX_MODE_NONE)

#define CHECK_GAMMA_NONE(c) (GET_OPT_GAMMA_MODE(c) == GST_VIDEO_GAMMA_MODE_NONE)
#define CHECK_GAMMA_REMAP(c) (GET_OPT_GAMMA_MODE(c) == GST_VIDEO_GAMMA_MODE_REMAP)

#define CHECK_PRIMARIES_NONE(c) (GET_OPT_PRIMARIES_MODE(c) == GST_VIDEO_PRIMARIES_MODE_NONE)
#define CHECK_PRIMARIES_MERGE(c) (GET_OPT_PRIMARIES_MODE(c) == GST_VIDEO_PRIMARIES_MODE_MERGE_ONLY)
#define CHECK_PRIMARIES_FAST(c) (GET_OPT_PRIMARIES_MODE(c) == GST_VIDEO_PRIMARIES_MODE_FAST)

#define CHECK_CHROMA_FULL(c) (GET_OPT_CHROMA_MODE(c) == GST_VIDEO_CHROMA_MODE_FULL)
#define CHECK_CHROMA_UPSAMPLE(c) (GET_OPT_CHROMA_MODE(c) == GST_VIDEO_CHROMA_MODE_UPSAMPLE_ONLY)
#define CHECK_CHROMA_DOWNSAMPLE(c) (GET_OPT_CHROMA_MODE(c) == GST_VIDEO_CHROMA_MODE_DOWNSAMPLE_ONLY)
#define CHECK_CHROMA_NONE(c) (GET_OPT_CHROMA_MODE(c) == GST_VIDEO_CHROMA_MODE_NONE)

static GstLineCache *
chain_unpack_line (GstVideoConverter * convert, gint idx)
{
  GstLineCache *prev;
  GstVideoInfo *info;

  info = &convert->in_info;

  convert->current_format = convert->unpack_format;
  convert->current_bits = convert->unpack_bits;
  convert->current_pstride = convert->current_bits >> 1;

  convert->unpack_pstride = convert->current_pstride;
  convert->identity_unpack = (convert->current_format == info->finfo->format);

  GST_DEBUG ("chain unpack line format %s, pstride %d, identity_unpack %d",
      gst_video_format_to_string (convert->current_format),
      convert->current_pstride, convert->identity_unpack);

  prev = convert->unpack_lines[idx] = gst_line_cache_new (NULL);
  prev->write_input = FALSE;
  prev->pass_alloc = FALSE;
  prev->n_lines = 1;
  prev->stride = convert->current_pstride * convert->current_width;
  gst_line_cache_set_need_line_func (prev, do_unpack_lines, idx, convert, NULL);

  return prev;
}

static GstLineCache *
chain_upsample (GstVideoConverter * convert, GstLineCache * prev, gint idx)
{
  video_converter_compute_resample (convert, idx);

  if (convert->upsample_p[idx] || convert->upsample_i[idx]) {
    GST_DEBUG ("chain upsample");
    prev = convert->upsample_lines[idx] = gst_line_cache_new (prev);
    prev->write_input = TRUE;
    prev->pass_alloc = TRUE;
    prev->n_lines = 4;
    prev->stride = convert->current_pstride * convert->current_width;
    gst_line_cache_set_need_line_func (prev,
        do_upsample_lines, idx, convert, NULL);
  }
  return prev;
}

static void
color_matrix_set_identity (MatrixData * m)
{
  int i, j;

  for (i = 0; i < 4; i++) {
    for (j = 0; j < 4; j++) {
      m->dm[i][j] = (i == j);
    }
  }
}

static void
color_matrix_copy (MatrixData * d, const MatrixData * s)
{
  gint i, j;

  for (i = 0; i < 4; i++)
    for (j = 0; j < 4; j++)
      d->dm[i][j] = s->dm[i][j];
}

/* Perform 4x4 matrix multiplication:
 *  - @dst@ = @a@ * @b@
 *  - @dst@ may be a pointer to @a@ andor @b@
 */
static void
color_matrix_multiply (MatrixData * dst, MatrixData * a, MatrixData * b)
{
  MatrixData tmp;
  int i, j, k;

  for (i = 0; i < 4; i++) {
    for (j = 0; j < 4; j++) {
      double x = 0;
      for (k = 0; k < 4; k++) {
        x += a->dm[i][k] * b->dm[k][j];
      }
      tmp.dm[i][j] = x;
    }
  }
  color_matrix_copy (dst, &tmp);
}

static void
color_matrix_invert (MatrixData * d, MatrixData * s)
{
  MatrixData tmp;
  int i, j;
  double det;

  color_matrix_set_identity (&tmp);
  for (j = 0; j < 3; j++) {
    for (i = 0; i < 3; i++) {
      tmp.dm[j][i] =
          s->dm[(i + 1) % 3][(j + 1) % 3] * s->dm[(i + 2) % 3][(j + 2) % 3] -
          s->dm[(i + 1) % 3][(j + 2) % 3] * s->dm[(i + 2) % 3][(j + 1) % 3];
    }
  }
  det =
      tmp.dm[0][0] * s->dm[0][0] + tmp.dm[0][1] * s->dm[1][0] +
      tmp.dm[0][2] * s->dm[2][0];
  for (j = 0; j < 3; j++) {
    for (i = 0; i < 3; i++) {
      tmp.dm[i][j] /= det;
    }
  }
  color_matrix_copy (d, &tmp);
}

static void
color_matrix_offset_components (MatrixData * m, double a1, double a2, double a3)
{
  MatrixData a;

  color_matrix_set_identity (&a);
  a.dm[0][3] = a1;
  a.dm[1][3] = a2;
  a.dm[2][3] = a3;
  color_matrix_multiply (m, &a, m);
}

static void
color_matrix_scale_components (MatrixData * m, double a1, double a2, double a3)
{
  MatrixData a;

  color_matrix_set_identity (&a);
  a.dm[0][0] = a1;
  a.dm[1][1] = a2;
  a.dm[2][2] = a3;
  color_matrix_multiply (m, &a, m);
}

static void
color_matrix_debug (const MatrixData * s)
{
  GST_DEBUG ("[%f %f %f %f]", s->dm[0][0], s->dm[0][1], s->dm[0][2],
      s->dm[0][3]);
  GST_DEBUG ("[%f %f %f %f]", s->dm[1][0], s->dm[1][1], s->dm[1][2],
      s->dm[1][3]);
  GST_DEBUG ("[%f %f %f %f]", s->dm[2][0], s->dm[2][1], s->dm[2][2],
      s->dm[2][3]);
  GST_DEBUG ("[%f %f %f %f]", s->dm[3][0], s->dm[3][1], s->dm[3][2],
      s->dm[3][3]);
}

static void
color_matrix_convert (MatrixData * s)
{
  gint i, j;

  for (i = 0; i < 4; i++)
    for (j = 0; j < 4; j++)
      s->im[i][j] = rint (s->dm[i][j]);

  GST_DEBUG ("[%6d %6d %6d %6d]", s->im[0][0], s->im[0][1], s->im[0][2],
      s->im[0][3]);
  GST_DEBUG ("[%6d %6d %6d %6d]", s->im[1][0], s->im[1][1], s->im[1][2],
      s->im[1][3]);
  GST_DEBUG ("[%6d %6d %6d %6d]", s->im[2][0], s->im[2][1], s->im[2][2],
      s->im[2][3]);
  GST_DEBUG ("[%6d %6d %6d %6d]", s->im[3][0], s->im[3][1], s->im[3][2],
      s->im[3][3]);
}

static void
color_matrix_YCbCr_to_RGB (MatrixData * m, double Kr, double Kb)
{
  double Kg = 1.0 - Kr - Kb;
  MatrixData k = {
    {
          {1., 0., 2 * (1 - Kr), 0.},
          {1., -2 * Kb * (1 - Kb) / Kg, -2 * Kr * (1 - Kr) / Kg, 0.},
          {1., 2 * (1 - Kb), 0., 0.},
          {0., 0., 0., 1.},
        }
  };

  color_matrix_multiply (m, &k, m);
}

static void
color_matrix_RGB_to_YCbCr (MatrixData * m, double Kr, double Kb)
{
  double Kg = 1.0 - Kr - Kb;
  MatrixData k;
  double x;

  k.dm[0][0] = Kr;
  k.dm[0][1] = Kg;
  k.dm[0][2] = Kb;
  k.dm[0][3] = 0;

  x = 1 / (2 * (1 - Kb));
  k.dm[1][0] = -x * Kr;
  k.dm[1][1] = -x * Kg;
  k.dm[1][2] = x * (1 - Kb);
  k.dm[1][3] = 0;

  x = 1 / (2 * (1 - Kr));
  k.dm[2][0] = x * (1 - Kr);
  k.dm[2][1] = -x * Kg;
  k.dm[2][2] = -x * Kb;
  k.dm[2][3] = 0;

  k.dm[3][0] = 0;
  k.dm[3][1] = 0;
  k.dm[3][2] = 0;
  k.dm[3][3] = 1;

  color_matrix_multiply (m, &k, m);
}

static void
color_matrix_RGB_to_XYZ (MatrixData * dst, double Rx, double Ry, double Gx,
    double Gy, double Bx, double By, double Wx, double Wy)
{
  MatrixData m, im;
  double sx, sy, sz;
  double wx, wy, wz;

  color_matrix_set_identity (&m);

  m.dm[0][0] = Rx;
  m.dm[1][0] = Ry;
  m.dm[2][0] = (1.0 - Rx - Ry);
  m.dm[0][1] = Gx;
  m.dm[1][1] = Gy;
  m.dm[2][1] = (1.0 - Gx - Gy);
  m.dm[0][2] = Bx;
  m.dm[1][2] = By;
  m.dm[2][2] = (1.0 - Bx - By);

  color_matrix_invert (&im, &m);

  wx = Wx / Wy;
  wy = 1.0;
  wz = (1.0 - Wx - Wy) / Wy;

  sx = im.dm[0][0] * wx + im.dm[0][1] * wy + im.dm[0][2] * wz;
  sy = im.dm[1][0] * wx + im.dm[1][1] * wy + im.dm[1][2] * wz;
  sz = im.dm[2][0] * wx + im.dm[2][1] * wy + im.dm[2][2] * wz;

  m.dm[0][0] *= sx;
  m.dm[1][0] *= sx;
  m.dm[2][0] *= sx;
  m.dm[0][1] *= sy;
  m.dm[1][1] *= sy;
  m.dm[2][1] *= sy;
  m.dm[0][2] *= sz;
  m.dm[1][2] *= sz;
  m.dm[2][2] *= sz;

  color_matrix_copy (dst, &m);
}

static void
videoconvert_convert_init_tables (MatrixData * data)
{
  gint i, j;

  data->t_r = g_new (gint64, 256);
  data->t_g = g_new (gint64, 256);
  data->t_b = g_new (gint64, 256);

  for (i = 0; i < 256; i++) {
    gint64 r = 0, g = 0, b = 0;

    for (j = 0; j < 3; j++) {
      r = (r << 16) + data->im[j][0] * i;
      g = (g << 16) + data->im[j][1] * i;
      b = (b << 16) + data->im[j][2] * i;
    }
    data->t_r[i] = r;
    data->t_g[i] = g;
    data->t_b[i] = b;
  }
  data->t_c = ((gint64) data->im[0][3] << 32)
      + ((gint64) data->im[1][3] << 16)
      + ((gint64) data->im[2][3] << 0);
}

void
_custom_video_orc_matrix8 (guint8 * ORC_RESTRICT d1,
    const guint8 * ORC_RESTRICT s1, orc_int64 p1, orc_int64 p2, orc_int64 p3,
    orc_int64 p4, int n)
{
  gint i;
  gint r, g, b;
  gint y, u, v;
  gint a00, a01, a02, a03;
  gint a10, a11, a12, a13;
  gint a20, a21, a22, a23;

  a00 = (gint16) (p1 >> 16);
  a01 = (gint16) (p2 >> 16);
  a02 = (gint16) (p3 >> 16);
  a03 = (gint16) (p4 >> 16);
  a10 = (gint16) (p1 >> 32);
  a11 = (gint16) (p2 >> 32);
  a12 = (gint16) (p3 >> 32);
  a13 = (gint16) (p4 >> 32);
  a20 = (gint16) (p1 >> 48);
  a21 = (gint16) (p2 >> 48);
  a22 = (gint16) (p3 >> 48);
  a23 = (gint16) (p4 >> 48);

  for (i = 0; i < n; i++) {
    r = s1[i * 4 + 1];
    g = s1[i * 4 + 2];
    b = s1[i * 4 + 3];

    y = ((a00 * r + a01 * g + a02 * b) >> SCALE) + a03;
    u = ((a10 * r + a11 * g + a12 * b) >> SCALE) + a13;
    v = ((a20 * r + a21 * g + a22 * b) >> SCALE) + a23;

    d1[i * 4 + 1] = CLAMP (y, 0, 255);
    d1[i * 4 + 2] = CLAMP (u, 0, 255);
    d1[i * 4 + 3] = CLAMP (v, 0, 255);
  }
}

static void
video_converter_matrix8 (MatrixData * data, gpointer pixels)
{
  gpointer d = pixels;
  video_orc_matrix8 (d, pixels, data->orc_p1, data->orc_p2,
      data->orc_p3, data->orc_p4, data->width);
}

static void
video_converter_matrix8_table (MatrixData * data, gpointer pixels)
{
  gint i, width = data->width * 4;
  guint8 r, g, b;
  gint64 c = data->t_c;
  guint8 *p = pixels;
  gint64 x;

  for (i = 0; i < width; i += 4) {
    r = p[i + 1];
    g = p[i + 2];
    b = p[i + 3];

    x = data->t_r[r] + data->t_g[g] + data->t_b[b] + c;

    p[i + 1] = x >> (32 + SCALE);
    p[i + 2] = x >> (16 + SCALE);
    p[i + 3] = x >> (0 + SCALE);
  }
}

static void
video_converter_matrix8_AYUV_ARGB (MatrixData * data, gpointer pixels)
{
  gpointer d = pixels;

  video_orc_convert_AYUV_ARGB (d, 0, pixels, 0,
      data->im[0][0], data->im[0][2],
      data->im[2][1], data->im[1][1], data->im[1][2], data->width, 1);
}

static gboolean
is_ayuv_to_rgb_matrix (MatrixData * data)
{
  if (data->im[0][0] != data->im[1][0] || data->im[1][0] != data->im[2][0])
    return FALSE;

  if (data->im[0][1] != 0 || data->im[2][2] != 0)
    return FALSE;

  return TRUE;
}

static gboolean
is_identity_matrix (MatrixData * data)
{
  gint i, j;
  gint c = data->im[0][0];

  /* not really checking identity because of rounding errors but given
   * the conversions we do we just check for anything that looks like:
   *
   *  c 0 0 0
   *  0 c 0 0
   *  0 0 c 0
   *  0 0 0 1
   */
  for (i = 0; i < 4; i++) {
    for (j = 0; j < 4; j++) {
      if (i == j) {
        if (i == 3 && data->im[i][j] != 1)
          return FALSE;
        else if (data->im[i][j] != c)
          return FALSE;
      } else if (data->im[i][j] != 0)
        return FALSE;
    }
  }
  return TRUE;
}

static gboolean
is_no_clip_matrix (MatrixData * data)
{
  gint i;
  static const guint8 test[8][3] = {
    {0, 0, 0},
    {0, 0, 255},
    {0, 255, 0},
    {0, 255, 255},
    {255, 0, 0},
    {255, 0, 255},
    {255, 255, 0},
    {255, 255, 255}
  };

  for (i = 0; i < 8; i++) {
    gint r, g, b;
    gint y, u, v;

    r = test[i][0];
    g = test[i][1];
    b = test[i][2];

    y = (data->im[0][0] * r + data->im[0][1] * g +
        data->im[0][2] * b + data->im[0][3]) >> SCALE;
    u = (data->im[1][0] * r + data->im[1][1] * g +
        data->im[1][2] * b + data->im[1][3]) >> SCALE;
    v = (data->im[2][0] * r + data->im[2][1] * g +
        data->im[2][2] * b + data->im[2][3]) >> SCALE;

    if (y != CLAMP (y, 0, 255) || u != CLAMP (u, 0, 255)
        || v != CLAMP (v, 0, 255))
      return FALSE;
  }
  return TRUE;
}

static void
video_converter_matrix16 (MatrixData * data, gpointer pixels)
{
  int i;
  int r, g, b;
  int y, u, v;
  guint16 *p = pixels;
  gint width = data->width;

  for (i = 0; i < width; i++) {
    r = p[i * 4 + 1];
    g = p[i * 4 + 2];
    b = p[i * 4 + 3];

    y = (data->im[0][0] * r + data->im[0][1] * g +
        data->im[0][2] * b + data->im[0][3]) >> SCALE;
    u = (data->im[1][0] * r + data->im[1][1] * g +
        data->im[1][2] * b + data->im[1][3]) >> SCALE;
    v = (data->im[2][0] * r + data->im[2][1] * g +
        data->im[2][2] * b + data->im[2][3]) >> SCALE;

    p[i * 4 + 1] = CLAMP (y, 0, 65535);
    p[i * 4 + 2] = CLAMP (u, 0, 65535);
    p[i * 4 + 3] = CLAMP (v, 0, 65535);
  }
}


static void
prepare_matrix (GstVideoConverter * convert, MatrixData * data)
{
  if (is_identity_matrix (data))
    return;

  color_matrix_scale_components (data, SCALE_F, SCALE_F, SCALE_F);
  color_matrix_convert (data);

  data->width = convert->current_width;

  if (convert->current_bits == 8) {
    if (!convert->unpack_rgb && convert->pack_rgb
        && is_ayuv_to_rgb_matrix (data)) {
      GST_DEBUG ("use fast AYUV -> RGB matrix");
      data->matrix_func = video_converter_matrix8_AYUV_ARGB;
    } else if (is_no_clip_matrix (data)) {
      GST_DEBUG ("use 8bit table");
      data->matrix_func = video_converter_matrix8_table;
      videoconvert_convert_init_tables (data);
    } else {
      gint a03, a13, a23;

      GST_DEBUG ("use 8bit matrix");
      data->matrix_func = video_converter_matrix8;

      data->orc_p1 = (((guint64) (guint16) data->im[2][0]) << 48) |
          (((guint64) (guint16) data->im[1][0]) << 32) |
          (((guint64) (guint16) data->im[0][0]) << 16);
      data->orc_p2 = (((guint64) (guint16) data->im[2][1]) << 48) |
          (((guint64) (guint16) data->im[1][1]) << 32) |
          (((guint64) (guint16) data->im[0][1]) << 16);
      data->orc_p3 = (((guint64) (guint16) data->im[2][2]) << 48) |
          (((guint64) (guint16) data->im[1][2]) << 32) |
          (((guint64) (guint16) data->im[0][2]) << 16);

      a03 = data->im[0][3] >> SCALE;
      a13 = data->im[1][3] >> SCALE;
      a23 = data->im[2][3] >> SCALE;

      data->orc_p4 = (((guint64) (guint16) a23) << 48) |
          (((guint64) (guint16) a13) << 32) | (((guint64) (guint16) a03) << 16);
    }
  } else {
    GST_DEBUG ("use 16bit matrix");
    data->matrix_func = video_converter_matrix16;
  }
}

static void
compute_matrix_to_RGB (GstVideoConverter * convert, MatrixData * data)
{
  GstVideoInfo *info;
  gdouble Kr = 0, Kb = 0;

  info = &convert->in_info;

  {
    const GstVideoFormatInfo *uinfo;
    gint offset[4], scale[4];

    uinfo = gst_video_format_get_info (convert->unpack_format);

    /* bring color components to [0..1.0] range */
    gst_video_color_range_offsets (info->colorimetry.range, uinfo, offset,
        scale);

    color_matrix_offset_components (data, -offset[0], -offset[1], -offset[2]);
    color_matrix_scale_components (data, 1 / ((float) scale[0]),
        1 / ((float) scale[1]), 1 / ((float) scale[2]));
  }

  if (!convert->unpack_rgb && !CHECK_MATRIX_NONE (convert)) {
    if (CHECK_MATRIX_OUTPUT (convert))
      info = &convert->out_info;

    /* bring components to R'G'B' space */
    if (gst_video_color_matrix_get_Kr_Kb (info->colorimetry.matrix, &Kr, &Kb))
      color_matrix_YCbCr_to_RGB (data, Kr, Kb);
  }
  color_matrix_debug (data);
}

static void
compute_matrix_to_YUV (GstVideoConverter * convert, MatrixData * data,
    gboolean force)
{
  GstVideoInfo *info;
  gdouble Kr = 0, Kb = 0;

  if (force || (!convert->pack_rgb && !CHECK_MATRIX_NONE (convert))) {
    if (CHECK_MATRIX_INPUT (convert))
      info = &convert->in_info;
    else
      info = &convert->out_info;

    /* bring components to YCbCr space */
    if (gst_video_color_matrix_get_Kr_Kb (info->colorimetry.matrix, &Kr, &Kb))
      color_matrix_RGB_to_YCbCr (data, Kr, Kb);
  }

  info = &convert->out_info;

  {
    const GstVideoFormatInfo *uinfo;
    gint offset[4], scale[4];

    uinfo = gst_video_format_get_info (convert->pack_format);

    /* bring color components to nominal range */
    gst_video_color_range_offsets (info->colorimetry.range, uinfo, offset,
        scale);

    color_matrix_scale_components (data, (float) scale[0], (float) scale[1],
        (float) scale[2]);
    color_matrix_offset_components (data, offset[0], offset[1], offset[2]);
  }

  color_matrix_debug (data);
}


static void
gamma_convert_u8_u16 (GammaData * data, gpointer dest, gpointer src)
{
  gint i;
  guint8 *s = src;
  guint16 *d = dest;
  guint16 *table = data->gamma_table;
  gint width = data->width * 4;

  for (i = 0; i < width; i += 4) {
    d[i + 0] = (s[i] << 8) | s[i];
    d[i + 1] = table[s[i + 1]];
    d[i + 2] = table[s[i + 2]];
    d[i + 3] = table[s[i + 3]];
  }
}

static void
gamma_convert_u16_u8 (GammaData * data, gpointer dest, gpointer src)
{
  gint i;
  guint16 *s = src;
  guint8 *d = dest;
  guint8 *table = data->gamma_table;
  gint width = data->width * 4;

  for (i = 0; i < width; i += 4) {
    d[i + 0] = s[i] >> 8;
    d[i + 1] = table[s[i + 1]];
    d[i + 2] = table[s[i + 2]];
    d[i + 3] = table[s[i + 3]];
  }
}

static void
gamma_convert_u16_u16 (GammaData * data, gpointer dest, gpointer src)
{
  gint i;
  guint16 *s = src;
  guint16 *d = dest;
  guint16 *table = data->gamma_table;
  gint width = data->width * 4;

  for (i = 0; i < width; i += 4) {
    d[i + 0] = s[i];
    d[i + 1] = table[s[i + 1]];
    d[i + 2] = table[s[i + 2]];
    d[i + 3] = table[s[i + 3]];
  }
}

static void
setup_gamma_decode (GstVideoConverter * convert)
{
  GstVideoTransferFunction func;
  guint16 *t;
  gint i;

  func = convert->in_info.colorimetry.transfer;

  convert->gamma_dec.width = convert->current_width;
  if (convert->current_bits == 8) {
    GST_DEBUG ("gamma decode 8->16: %d", func);
    convert->gamma_dec.gamma_func = gamma_convert_u8_u16;
    t = convert->gamma_dec.gamma_table = g_malloc (sizeof (guint16) * 256);

    for (i = 0; i < 256; i++)
      t[i] = rint (gst_video_color_transfer_decode (func, i / 255.0) * 65535.0);
  } else {
    GST_DEBUG ("gamma decode 16->16: %d", func);
    convert->gamma_dec.gamma_func = gamma_convert_u16_u16;
    t = convert->gamma_dec.gamma_table = g_malloc (sizeof (guint16) * 65536);

    for (i = 0; i < 65536; i++)
      t[i] =
          rint (gst_video_color_transfer_decode (func, i / 65535.0) * 65535.0);
  }
  convert->current_bits = 16;
  convert->current_pstride = 8;
  convert->current_format = GST_VIDEO_FORMAT_ARGB64;
}

static void
setup_gamma_encode (GstVideoConverter * convert, gint target_bits)
{
  GstVideoTransferFunction func;
  gint i;

  func = convert->out_info.colorimetry.transfer;

  convert->gamma_enc.width = convert->current_width;
  if (target_bits == 8) {
    guint8 *t;

    GST_DEBUG ("gamma encode 16->8: %d", func);
    convert->gamma_enc.gamma_func = gamma_convert_u16_u8;
    t = convert->gamma_enc.gamma_table = g_malloc (sizeof (guint8) * 65536);

    for (i = 0; i < 65536; i++)
      t[i] = rint (gst_video_color_transfer_encode (func, i / 65535.0) * 255.0);
  } else {
    guint16 *t;

    GST_DEBUG ("gamma encode 16->16: %d", func);
    convert->gamma_enc.gamma_func = gamma_convert_u16_u16;
    t = convert->gamma_enc.gamma_table = g_malloc (sizeof (guint16) * 65536);

    for (i = 0; i < 65536; i++)
      t[i] =
          rint (gst_video_color_transfer_encode (func, i / 65535.0) * 65535.0);
  }
}

static GstLineCache *
chain_convert_to_RGB (GstVideoConverter * convert, GstLineCache * prev,
    gint idx)
{
  gboolean do_gamma;

  do_gamma = CHECK_GAMMA_REMAP (convert);

  if (do_gamma) {
    gint scale;

    if (!convert->unpack_rgb) {
      color_matrix_set_identity (&convert->to_RGB_matrix);
      compute_matrix_to_RGB (convert, &convert->to_RGB_matrix);

      /* matrix is in 0..1 range, scale to current bits */
      GST_DEBUG ("chain RGB convert");
      scale = 1 << convert->current_bits;
      color_matrix_scale_components (&convert->to_RGB_matrix,
          (float) scale, (float) scale, (float) scale);

      prepare_matrix (convert, &convert->to_RGB_matrix);

      if (convert->current_bits == 8)
        convert->current_format = GST_VIDEO_FORMAT_ARGB;
      else
        convert->current_format = GST_VIDEO_FORMAT_ARGB64;
    }

    prev = convert->to_RGB_lines[idx] = gst_line_cache_new (prev);
    prev->write_input = TRUE;
    prev->pass_alloc = FALSE;
    prev->n_lines = 1;
    prev->stride = convert->current_pstride * convert->current_width;
    gst_line_cache_set_need_line_func (prev,
        do_convert_to_RGB_lines, idx, convert, NULL);

    GST_DEBUG ("chain gamma decode");
    setup_gamma_decode (convert);
  }
  return prev;
}

static GstLineCache *
chain_hscale (GstVideoConverter * convert, GstLineCache * prev, gint idx)
{
  gint method;
  guint taps;

  method = GET_OPT_RESAMPLER_METHOD (convert);
  taps = GET_OPT_RESAMPLER_TAPS (convert);

  convert->h_scaler[idx] =
      gst_video_scaler_new (method, GST_VIDEO_SCALER_FLAG_NONE, taps,
      convert->in_width, convert->out_width, convert->config);

  gst_video_scaler_get_coeff (convert->h_scaler[idx], 0, NULL, &taps);

  GST_DEBUG ("chain hscale %d->%d, taps %d, method %d",
      convert->in_width, convert->out_width, taps, method);

  convert->current_width = convert->out_width;
  convert->h_scale_format = convert->current_format;

  prev = convert->hscale_lines[idx] = gst_line_cache_new (prev);
  prev->write_input = FALSE;
  prev->pass_alloc = FALSE;
  prev->n_lines = 1;
  prev->stride = convert->current_pstride * convert->current_width;
  gst_line_cache_set_need_line_func (prev, do_hscale_lines, idx, convert, NULL);

  return prev;
}

static GstLineCache *
chain_vscale (GstVideoConverter * convert, GstLineCache * prev, gint idx)
{
  gint method;
  guint taps, taps_i = 0;
  gint backlog = 0;

  method = GET_OPT_RESAMPLER_METHOD (convert);
  taps = GET_OPT_RESAMPLER_TAPS (convert);

  if (GST_VIDEO_INFO_IS_INTERLACED (&convert->in_info)) {
    convert->v_scaler_i[idx] =
        gst_video_scaler_new (method, GST_VIDEO_SCALER_FLAG_INTERLACED,
        taps, convert->in_height, convert->out_height, convert->config);

    gst_video_scaler_get_coeff (convert->v_scaler_i[idx], 0, NULL, &taps_i);
    backlog = taps_i;
  }
  convert->v_scaler_p[idx] =
      gst_video_scaler_new (method, 0, taps, convert->in_height,
      convert->out_height, convert->config);
  convert->v_scale_width = convert->current_width;
  convert->v_scale_format = convert->current_format;
  convert->current_height = convert->out_height;

  gst_video_scaler_get_coeff (convert->v_scaler_p[idx], 0, NULL, &taps);

  GST_DEBUG ("chain vscale %d->%d, taps %d, method %d, backlog %d",
      convert->in_height, convert->out_height, taps, method, backlog);

  prev->backlog = backlog;
  prev = convert->vscale_lines[idx] = gst_line_cache_new (prev);
  prev->pass_alloc = (taps == 1);
  prev->write_input = FALSE;
  prev->n_lines = MAX (taps_i, taps);
  prev->stride = convert->current_pstride * convert->current_width;
  gst_line_cache_set_need_line_func (prev, do_vscale_lines, idx, convert, NULL);

  return prev;
}

static GstLineCache *
chain_scale (GstVideoConverter * convert, GstLineCache * prev, gboolean force,
    gint idx)
{
  gint s0, s1, s2, s3;

  s0 = convert->current_width * convert->current_height;
  s3 = convert->out_width * convert->out_height;

  GST_DEBUG ("in pixels %d <> out pixels %d", s0, s3);

  if (s3 <= s0 || force) {
    /* we are making the image smaller or are forced to resample */
    s1 = convert->out_width * convert->current_height;
    s2 = convert->current_width * convert->out_height;

    GST_DEBUG ("%d <> %d", s1, s2);

    if (s1 <= s2) {
      /* h scaling first produces less pixels */
      if (convert->current_width != convert->out_width)
        prev = chain_hscale (convert, prev, idx);
      if (convert->current_height != convert->out_height)
        prev = chain_vscale (convert, prev, idx);
    } else {
      /* v scaling first produces less pixels */
      if (convert->current_height != convert->out_height)
        prev = chain_vscale (convert, prev, idx);
      if (convert->current_width != convert->out_width)
        prev = chain_hscale (convert, prev, idx);
    }
  }
  return prev;
}

static GstLineCache *
chain_convert (GstVideoConverter * convert, GstLineCache * prev, gint idx)
{
  gboolean do_gamma, do_conversion, pass_alloc = FALSE;
  gboolean same_matrix, same_primaries, same_bits;
  MatrixData p1, p2;

  same_bits = convert->unpack_bits == convert->pack_bits;
  if (CHECK_MATRIX_NONE (convert)) {
    same_matrix = TRUE;
  } else {
    same_matrix =
        convert->in_info.colorimetry.matrix ==
        convert->out_info.colorimetry.matrix;
  }

  if (CHECK_PRIMARIES_NONE (convert)) {
    same_primaries = TRUE;
  } else {
    same_primaries =
        convert->in_info.colorimetry.primaries ==
        convert->out_info.colorimetry.primaries;
  }

  GST_DEBUG ("matrix %d -> %d (%d)", convert->in_info.colorimetry.matrix,
      convert->out_info.colorimetry.matrix, same_matrix);
  GST_DEBUG ("bits %d -> %d (%d)", convert->unpack_bits, convert->pack_bits,
      same_bits);
  GST_DEBUG ("primaries %d -> %d (%d)", convert->in_info.colorimetry.primaries,
      convert->out_info.colorimetry.primaries, same_primaries);

  color_matrix_set_identity (&convert->convert_matrix);

  if (!same_primaries) {
    const GstVideoColorPrimariesInfo *pi;

    pi = gst_video_color_primaries_get_info (convert->in_info.colorimetry.
        primaries);
    color_matrix_RGB_to_XYZ (&p1, pi->Rx, pi->Ry, pi->Gx, pi->Gy, pi->Bx,
        pi->By, pi->Wx, pi->Wy);
    GST_DEBUG ("to XYZ matrix");
    color_matrix_debug (&p1);
    GST_DEBUG ("current matrix");
    color_matrix_multiply (&convert->convert_matrix, &convert->convert_matrix,
        &p1);
    color_matrix_debug (&convert->convert_matrix);

    pi = gst_video_color_primaries_get_info (convert->out_info.colorimetry.
        primaries);
    color_matrix_RGB_to_XYZ (&p2, pi->Rx, pi->Ry, pi->Gx, pi->Gy, pi->Bx,
        pi->By, pi->Wx, pi->Wy);
    color_matrix_invert (&p2, &p2);
    GST_DEBUG ("to RGB matrix");
    color_matrix_debug (&p2);
    color_matrix_multiply (&convert->convert_matrix, &convert->convert_matrix,
        &p2);
    GST_DEBUG ("current matrix");
    color_matrix_debug (&convert->convert_matrix);
  }

  do_gamma = CHECK_GAMMA_REMAP (convert);
  if (!do_gamma) {

    convert->in_bits = convert->unpack_bits;
    convert->out_bits = convert->pack_bits;

    if (!same_bits || !same_matrix || !same_primaries) {
      /* no gamma, combine all conversions into 1 */
      if (convert->in_bits < convert->out_bits) {
        gint scale = 1 << (convert->out_bits - convert->in_bits);
        color_matrix_scale_components (&convert->convert_matrix,
            1 / (float) scale, 1 / (float) scale, 1 / (float) scale);
      }
      GST_DEBUG ("to RGB matrix");
      compute_matrix_to_RGB (convert, &convert->convert_matrix);
      GST_DEBUG ("current matrix");
      color_matrix_debug (&convert->convert_matrix);

      GST_DEBUG ("to YUV matrix");
      compute_matrix_to_YUV (convert, &convert->convert_matrix, FALSE);
      GST_DEBUG ("current matrix");
      color_matrix_debug (&convert->convert_matrix);
      if (convert->in_bits > convert->out_bits) {
        gint scale = 1 << (convert->in_bits - convert->out_bits);
        color_matrix_scale_components (&convert->convert_matrix,
            (float) scale, (float) scale, (float) scale);
      }
      convert->current_bits = MAX (convert->in_bits, convert->out_bits);

      do_conversion = TRUE;
      if (!same_matrix || !same_primaries)
        prepare_matrix (convert, &convert->convert_matrix);
      if (convert->in_bits == convert->out_bits)
        pass_alloc = TRUE;
    } else
      do_conversion = FALSE;

    convert->current_bits = convert->pack_bits;
    convert->current_format = convert->pack_format;
    convert->current_pstride = convert->current_bits >> 1;
  } else {
    /* we did gamma, just do colorspace conversion if needed */
    if (same_primaries) {
      do_conversion = FALSE;
    } else {
      prepare_matrix (convert, &convert->convert_matrix);
      convert->in_bits = convert->out_bits = 16;
      pass_alloc = TRUE;
      do_conversion = TRUE;
    }
  }

  if (do_conversion) {
    GST_DEBUG ("chain conversion");
    prev = convert->convert_lines[idx] = gst_line_cache_new (prev);
    prev->write_input = TRUE;
    prev->pass_alloc = pass_alloc;
    prev->n_lines = 1;
    prev->stride = convert->current_pstride * convert->current_width;
    gst_line_cache_set_need_line_func (prev,
        do_convert_lines, idx, convert, NULL);
  }
  return prev;
}

static void
convert_set_alpha_u8 (GstVideoConverter * convert, gpointer pixels, gint width)
{
  guint8 *p = pixels;
  guint8 alpha = MIN (convert->alpha_value, 255);
  int i;

  for (i = 0; i < width; i++)
    p[i * 4] = alpha;
}

static void
convert_set_alpha_u16 (GstVideoConverter * convert, gpointer pixels, gint width)
{
  guint16 *p = pixels;
  guint16 alpha;
  int i;

  alpha = MIN (convert->alpha_value, 255);
  alpha |= alpha << 8;

  for (i = 0; i < width; i++)
    p[i * 4] = alpha;
}

static void
convert_mult_alpha_u8 (GstVideoConverter * convert, gpointer pixels, gint width)
{
  guint8 *p = pixels;
  guint alpha = convert->alpha_value;
  int i;

  for (i = 0; i < width; i++) {
    gint a = (p[i * 4] * alpha) / 255;
    p[i * 4] = CLAMP (a, 0, 255);
  }
}

static void
convert_mult_alpha_u16 (GstVideoConverter * convert, gpointer pixels,
    gint width)
{
  guint16 *p = pixels;
  guint alpha = convert->alpha_value;
  int i;

  for (i = 0; i < width; i++) {
    gint a = (p[i * 4] * alpha) / 255;
    p[i * 4] = CLAMP (a, 0, 65535);
  }
}

static GstLineCache *
chain_alpha (GstVideoConverter * convert, GstLineCache * prev, gint idx)
{
  switch (convert->alpha_mode) {
    case ALPHA_MODE_NONE:
    case ALPHA_MODE_COPY:
      return prev;

    case ALPHA_MODE_SET:
      if (convert->current_bits == 8)
        convert->alpha_func = convert_set_alpha_u8;
      else
        convert->alpha_func = convert_set_alpha_u16;
      break;
    case ALPHA_MODE_MULT:
      if (convert->current_bits == 8)
        convert->alpha_func = convert_mult_alpha_u8;
      else
        convert->alpha_func = convert_mult_alpha_u16;
      break;
  }

  GST_DEBUG ("chain alpha mode %d", convert->alpha_mode);
  prev = convert->alpha_lines[idx] = gst_line_cache_new (prev);
  prev->write_input = TRUE;
  prev->pass_alloc = TRUE;
  prev->n_lines = 1;
  prev->stride = convert->current_pstride * convert->current_width;
  gst_line_cache_set_need_line_func (prev, do_alpha_lines, idx, convert, NULL);

  return prev;
}

static GstLineCache *
chain_convert_to_YUV (GstVideoConverter * convert, GstLineCache * prev,
    gint idx)
{
  gboolean do_gamma;

  do_gamma = CHECK_GAMMA_REMAP (convert);

  if (do_gamma) {
    gint scale;

    GST_DEBUG ("chain gamma encode");
    setup_gamma_encode (convert, convert->pack_bits);

    convert->current_bits = convert->pack_bits;
    convert->current_pstride = convert->current_bits >> 1;

    if (!convert->pack_rgb) {
      color_matrix_set_identity (&convert->to_YUV_matrix);
      compute_matrix_to_YUV (convert, &convert->to_YUV_matrix, FALSE);

      /* matrix is in 0..255 range, scale to pack bits */
      GST_DEBUG ("chain YUV convert");
      scale = 1 << convert->pack_bits;
      color_matrix_scale_components (&convert->to_YUV_matrix,
          1 / (float) scale, 1 / (float) scale, 1 / (float) scale);
      prepare_matrix (convert, &convert->to_YUV_matrix);
    }
    convert->current_format = convert->pack_format;

    prev = convert->to_YUV_lines[idx] = gst_line_cache_new (prev);
    prev->write_input = FALSE;
    prev->pass_alloc = FALSE;
    prev->n_lines = 1;
    prev->stride = convert->current_pstride * convert->current_width;
    gst_line_cache_set_need_line_func (prev,
        do_convert_to_YUV_lines, idx, convert, NULL);
  }

  return prev;
}

static GstLineCache *
chain_downsample (GstVideoConverter * convert, GstLineCache * prev, gint idx)
{
  if (convert->downsample_p[idx] || convert->downsample_i[idx]) {
    GST_DEBUG ("chain downsample");
    prev = convert->downsample_lines[idx] = gst_line_cache_new (prev);
    prev->write_input = TRUE;
    prev->pass_alloc = TRUE;
    prev->n_lines = 4;
    prev->stride = convert->current_pstride * convert->current_width;
    gst_line_cache_set_need_line_func (prev,
        do_downsample_lines, idx, convert, NULL);
  }
  return prev;
}

static GstLineCache *
chain_dither (GstVideoConverter * convert, GstLineCache * prev, gint idx)
{
  gint i;
  gboolean do_dither = FALSE;
  GstVideoDitherFlags flags = 0;
  GstVideoDitherMethod method;
  guint quant[4], target_quant;

  method = GET_OPT_DITHER_METHOD (convert);
  if (method == GST_VIDEO_DITHER_NONE)
    return prev;

  target_quant = GET_OPT_DITHER_QUANTIZATION (convert);
  GST_DEBUG ("method %d, target-quantization %d", method, target_quant);

  if (convert->pack_pal) {
    quant[0] = 47;
    quant[1] = 47;
    quant[2] = 47;
    quant[3] = 1;
    do_dither = TRUE;
  } else {
    for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) {
      gint depth;

      depth = convert->out_info.finfo->depth[i];

      if (depth == 0) {
        quant[i] = 0;
        continue;
      }

      if (convert->current_bits >= depth) {
        quant[i] = 1 << (convert->current_bits - depth);
        if (target_quant > quant[i]) {
          flags |= GST_VIDEO_DITHER_FLAG_QUANTIZE;
          quant[i] = target_quant;
        }
      } else {
        quant[i] = 0;
      }
      if (quant[i] > 1)
        do_dither = TRUE;
    }
  }

  if (do_dither) {
    GST_DEBUG ("chain dither");

    convert->dither[idx] = gst_video_dither_new (method,
        flags, convert->pack_format, quant, convert->current_width);

    prev = convert->dither_lines[idx] = gst_line_cache_new (prev);
    prev->write_input = TRUE;
    prev->pass_alloc = TRUE;
    prev->n_lines = 1;
    prev->stride = convert->current_pstride * convert->current_width;
    gst_line_cache_set_need_line_func (prev, do_dither_lines, idx, convert,
        NULL);
  }
  return prev;
}

static GstLineCache *
chain_pack (GstVideoConverter * convert, GstLineCache * prev, gint idx)
{
  convert->pack_nlines = convert->out_info.finfo->pack_lines;
  convert->pack_pstride = convert->current_pstride;
  convert->identity_pack =
      (convert->out_info.finfo->format ==
      convert->out_info.finfo->unpack_format);
  GST_DEBUG ("chain pack line format %s, pstride %d, identity_pack %d (%d %d)",
      gst_video_format_to_string (convert->current_format),
      convert->current_pstride, convert->identity_pack,
      convert->out_info.finfo->format, convert->out_info.finfo->unpack_format);

  return prev;
}

static void
setup_allocators (GstVideoConverter * convert)
{
  GstLineCache *cache, *prev;
  GstLineCacheAllocLineFunc alloc_line;
  gboolean alloc_writable;
  gpointer user_data;
  GDestroyNotify notify;
  gint width;
  gint i;

  width = MAX (convert->in_maxwidth, convert->out_maxwidth);
  width += convert->out_x;

  for (i = 0; i < convert->conversion_runner->n_threads; i++) {
    /* start with using dest lines if we can directly write into it */
    if (convert->identity_pack) {
      alloc_line = get_dest_line;
      alloc_writable = TRUE;
      user_data = convert;
      notify = NULL;
    } else {
      user_data =
          converter_alloc_new (sizeof (guint16) * width * 4, 4 + BACKLOG,
          convert, NULL);
      setup_border_alloc (convert, user_data);
      notify = (GDestroyNotify) converter_alloc_free;
      alloc_line = get_border_temp_line;
      /* when we add a border, we need to write */
      alloc_writable = convert->borderline != NULL;
    }

    /* First step, try to calculate how many temp lines we need. Go backwards,
     * keep track of the maximum number of lines we need for each intermediate
     * step.  */
    for (prev = cache = convert->pack_lines[i]; cache; cache = cache->prev) {
      GST_DEBUG ("looking at cache %p, %d lines, %d backlog", cache,
          cache->n_lines, cache->backlog);
      prev->n_lines = MAX (prev->n_lines, cache->n_lines);
      if (!cache->pass_alloc) {
        GST_DEBUG ("cache %p, needs %d lines", prev, prev->n_lines);
        prev = cache;
      }
    }

    /* now walk backwards, we try to write into the dest lines directly
     * and keep track if the source needs to be writable */
    for (cache = convert->pack_lines[i]; cache; cache = cache->prev) {
      gst_line_cache_set_alloc_line_func (cache, alloc_line, user_data, notify);
      cache->alloc_writable = alloc_writable;

      /* make sure only one cache frees the allocator */
      notify = NULL;

      if (!cache->pass_alloc) {
        /* can't pass allocator, make new temp line allocator */
        user_data =
            converter_alloc_new (sizeof (guint16) * width * 4,
            cache->n_lines + cache->backlog, convert, NULL);
        notify = (GDestroyNotify) converter_alloc_free;
        alloc_line = get_temp_line;
        alloc_writable = FALSE;
      }
      /* if someone writes to the input, we need a writable line from the
       * previous cache */
      if (cache->write_input)
        alloc_writable = TRUE;
    }
    /* free leftover allocator */
    if (notify)
      notify (user_data);
  }
}

static void
setup_borderline (GstVideoConverter * convert)
{
  gint width;

  width = MAX (convert->in_maxwidth, convert->out_maxwidth);
  width += convert->out_x;

  if (convert->fill_border && (convert->out_height < convert->out_maxheight ||
          convert->out_width < convert->out_maxwidth)) {
    guint32 border_val;
    gint i, w_sub;
    const GstVideoFormatInfo *out_finfo;
    gpointer planes[GST_VIDEO_MAX_PLANES];
    gint strides[GST_VIDEO_MAX_PLANES];

    convert->borderline = g_malloc0 (sizeof (guint16) * width * 4);

    out_finfo = convert->out_info.finfo;

    if (GST_VIDEO_INFO_IS_YUV (&convert->out_info)) {
      MatrixData cm;
      gint a, r, g, b;
      gint y, u, v;

      /* Get Color matrix. */
      color_matrix_set_identity (&cm);
      compute_matrix_to_YUV (convert, &cm, TRUE);
      color_matrix_convert (&cm);

      border_val = GINT32_FROM_BE (convert->border_argb);

      b = (0xFF000000 & border_val) >> 24;
      g = (0x00FF0000 & border_val) >> 16;
      r = (0x0000FF00 & border_val) >> 8;
      a = (0x000000FF & border_val);

      y = 16 + ((r * cm.im[0][0] + g * cm.im[0][1] + b * cm.im[0][2]) >> 8);
      u = 128 + ((r * cm.im[1][0] + g * cm.im[1][1] + b * cm.im[1][2]) >> 8);
      v = 128 + ((r * cm.im[2][0] + g * cm.im[2][1] + b * cm.im[2][2]) >> 8);

      a = CLAMP (a, 0, 255);
      y = CLAMP (y, 0, 255);
      u = CLAMP (u, 0, 255);
      v = CLAMP (v, 0, 255);

      border_val = a | (y << 8) | (u << 16) | ((guint32) v << 24);
    } else {
      border_val = GINT32_FROM_BE (convert->border_argb);
    }
    if (convert->pack_bits == 8)
      video_orc_splat_u32 (convert->borderline, border_val, width);
    else
      video_orc_splat2_u64 (convert->borderline, border_val, width);

    /* convert pixels */
    for (i = 0; i < out_finfo->n_planes; i++) {
      planes[i] = &convert->borders[i];
      strides[i] = sizeof (guint64);
    }
    w_sub = 0;
    if (out_finfo->n_planes == 1) {
      /* for packed formats, convert based on subsampling so that we
       * get a complete group of pixels */
      for (i = 0; i < out_finfo->n_components; i++) {
        w_sub = MAX (w_sub, out_finfo->w_sub[i]);
      }
    }
    out_finfo->pack_func (out_finfo, GST_VIDEO_PACK_FLAG_NONE,
        convert->borderline, 0, planes, strides,
        GST_VIDEO_CHROMA_SITE_UNKNOWN, 0, 1 << w_sub);
  } else {
    convert->borderline = NULL;
  }
}

static AlphaMode
convert_get_alpha_mode (GstVideoConverter * convert)
{
  gboolean in_alpha, out_alpha;

  in_alpha = GST_VIDEO_INFO_HAS_ALPHA (&convert->in_info);
  out_alpha = GST_VIDEO_INFO_HAS_ALPHA (&convert->out_info);

  /* no output alpha, do nothing */
  if (!out_alpha)
    return ALPHA_MODE_NONE;

  if (in_alpha) {
    /* in and out */
    if (CHECK_ALPHA_COPY (convert))
      return ALPHA_MODE_COPY;

    if (CHECK_ALPHA_MULT (convert)) {
      if (GET_OPT_ALPHA_VALUE (convert) == 1.0)
        return ALPHA_MODE_COPY;
      else
        return ALPHA_MODE_MULT;
    }
  }
  /* nothing special, this is what unpack etc does automatically */
  if (GET_OPT_ALPHA_VALUE (convert) == 1.0)
    return ALPHA_MODE_NONE;

  /* everything else becomes SET */
  return ALPHA_MODE_SET;
}

/**
 * gst_video_converter_new: (skip)
 * @in_info: a #GstVideoInfo
 * @out_info: a #GstVideoInfo
 * @config: (transfer full): a #GstStructure with configuration options
 *
 * Create a new converter object to convert between @in_info and @out_info
 * with @config.
 *
 * Returns: a #GstVideoConverter or %NULL if conversion is not possible.
 *
 * Since: 1.6
 */
GstVideoConverter *
gst_video_converter_new (GstVideoInfo * in_info, GstVideoInfo * out_info,
    GstStructure * config)
{
  GstVideoConverter *convert;
  GstLineCache *prev;
  const GstVideoFormatInfo *fin, *fout, *finfo;
  gdouble alpha_value;
  gint n_threads, i;

  g_return_val_if_fail (in_info != NULL, NULL);
  g_return_val_if_fail (out_info != NULL, NULL);
  /* we won't ever do framerate conversion */
  g_return_val_if_fail (in_info->fps_n == out_info->fps_n, NULL);
  g_return_val_if_fail (in_info->fps_d == out_info->fps_d, NULL);
  /* we won't ever do deinterlace */
  g_return_val_if_fail (in_info->interlace_mode == out_info->interlace_mode,
      NULL);

  convert = g_slice_new0 (GstVideoConverter);

  fin = in_info->finfo;
  fout = out_info->finfo;

  convert->in_info = *in_info;
  convert->out_info = *out_info;

  /* default config */
  convert->config = gst_structure_new_empty ("GstVideoConverter");
  if (config)
    gst_video_converter_set_config (convert, config);

  convert->in_maxwidth = GST_VIDEO_INFO_WIDTH (in_info);
  convert->in_maxheight = GST_VIDEO_INFO_HEIGHT (in_info);
  convert->out_maxwidth = GST_VIDEO_INFO_WIDTH (out_info);
  convert->out_maxheight = GST_VIDEO_INFO_HEIGHT (out_info);

  convert->in_x = get_opt_int (convert, GST_VIDEO_CONVERTER_OPT_SRC_X, 0);
  convert->in_y = get_opt_int (convert, GST_VIDEO_CONVERTER_OPT_SRC_Y, 0);
  convert->in_x &= ~((1 << fin->w_sub[1]) - 1);
  convert->in_y &= ~((1 << fin->h_sub[1]) - 1);

  convert->in_width = get_opt_int (convert,
      GST_VIDEO_CONVERTER_OPT_SRC_WIDTH, convert->in_maxwidth - convert->in_x);
  convert->in_height = get_opt_int (convert,
      GST_VIDEO_CONVERTER_OPT_SRC_HEIGHT,
      convert->in_maxheight - convert->in_y);

  convert->in_width =
      MIN (convert->in_width, convert->in_maxwidth - convert->in_x);
  convert->in_height =
      MIN (convert->in_height, convert->in_maxheight - convert->in_y);

  convert->out_x = get_opt_int (convert, GST_VIDEO_CONVERTER_OPT_DEST_X, 0);
  convert->out_y = get_opt_int (convert, GST_VIDEO_CONVERTER_OPT_DEST_Y, 0);
  convert->out_x &= ~((1 << fout->w_sub[1]) - 1);
  convert->out_y &= ~((1 << fout->h_sub[1]) - 1);

  convert->out_width = get_opt_int (convert,
      GST_VIDEO_CONVERTER_OPT_DEST_WIDTH,
      convert->out_maxwidth - convert->out_x);
  convert->out_height =
      get_opt_int (convert, GST_VIDEO_CONVERTER_OPT_DEST_HEIGHT,
      convert->out_maxheight - convert->out_y);

  convert->out_width =
      MIN (convert->out_width, convert->out_maxwidth - convert->out_x);
  convert->out_height =
      MIN (convert->out_height, convert->out_maxheight - convert->out_y);

  convert->fill_border = GET_OPT_FILL_BORDER (convert);
  convert->border_argb = get_opt_uint (convert,
      GST_VIDEO_CONVERTER_OPT_BORDER_ARGB, DEFAULT_OPT_BORDER_ARGB);

  alpha_value = GET_OPT_ALPHA_VALUE (convert);
  convert->alpha_value = 255 * alpha_value;
  convert->alpha_mode = convert_get_alpha_mode (convert);

  convert->unpack_format = in_info->finfo->unpack_format;
  finfo = gst_video_format_get_info (convert->unpack_format);
  convert->unpack_bits = GST_VIDEO_FORMAT_INFO_DEPTH (finfo, 0);
  convert->unpack_rgb = GST_VIDEO_FORMAT_INFO_IS_RGB (finfo);
  if (convert->unpack_rgb
      && in_info->colorimetry.matrix != GST_VIDEO_COLOR_MATRIX_RGB) {
    /* force identity matrix for RGB input */
    GST_WARNING ("invalid matrix %d for input RGB format, using RGB",
        in_info->colorimetry.matrix);
    convert->in_info.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
  }

  convert->pack_format = out_info->finfo->unpack_format;
  finfo = gst_video_format_get_info (convert->pack_format);
  convert->pack_bits = GST_VIDEO_FORMAT_INFO_DEPTH (finfo, 0);
  convert->pack_rgb = GST_VIDEO_FORMAT_INFO_IS_RGB (finfo);
  convert->pack_pal =
      gst_video_format_get_palette (GST_VIDEO_INFO_FORMAT (out_info),
      &convert->pack_palsize);
  if (convert->pack_rgb
      && out_info->colorimetry.matrix != GST_VIDEO_COLOR_MATRIX_RGB) {
    /* force identity matrix for RGB output */
    GST_WARNING ("invalid matrix %d for output RGB format, using RGB",
        out_info->colorimetry.matrix);
    convert->out_info.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
  }

  n_threads = get_opt_uint (convert, GST_VIDEO_CONVERTER_OPT_THREADS, 1);
  if (n_threads == 0 || n_threads > g_get_num_processors ())
    n_threads = g_get_num_processors ();
  /* Magic number of 200 lines */
  if (MAX (convert->out_height, convert->in_height) / n_threads < 200)
    n_threads = (MAX (convert->out_height, convert->in_height) + 199) / 200;
  convert->conversion_runner = gst_parallelized_task_runner_new (n_threads);

  if (video_converter_lookup_fastpath (convert))
    goto done;

  if (in_info->finfo->unpack_func == NULL)
    goto no_unpack_func;

  if (out_info->finfo->pack_func == NULL)
    goto no_pack_func;

  convert->convert = video_converter_generic;

  convert->upsample_p = g_new0 (GstVideoChromaResample *, n_threads);
  convert->upsample_i = g_new0 (GstVideoChromaResample *, n_threads);
  convert->downsample_p = g_new0 (GstVideoChromaResample *, n_threads);
  convert->downsample_i = g_new0 (GstVideoChromaResample *, n_threads);
  convert->v_scaler_p = g_new0 (GstVideoScaler *, n_threads);
  convert->v_scaler_i = g_new0 (GstVideoScaler *, n_threads);
  convert->h_scaler = g_new0 (GstVideoScaler *, n_threads);
  convert->unpack_lines = g_new0 (GstLineCache *, n_threads);
  convert->pack_lines = g_new0 (GstLineCache *, n_threads);
  convert->upsample_lines = g_new0 (GstLineCache *, n_threads);
  convert->to_RGB_lines = g_new0 (GstLineCache *, n_threads);
  convert->hscale_lines = g_new0 (GstLineCache *, n_threads);
  convert->vscale_lines = g_new0 (GstLineCache *, n_threads);
  convert->convert_lines = g_new0 (GstLineCache *, n_threads);
  convert->alpha_lines = g_new0 (GstLineCache *, n_threads);
  convert->to_YUV_lines = g_new0 (GstLineCache *, n_threads);
  convert->downsample_lines = g_new0 (GstLineCache *, n_threads);
  convert->dither_lines = g_new0 (GstLineCache *, n_threads);
  convert->dither = g_new0 (GstVideoDither *, n_threads);

  for (i = 0; i < n_threads; i++) {
    convert->current_format = GST_VIDEO_INFO_FORMAT (in_info);
    convert->current_width = convert->in_width;
    convert->current_height = convert->in_height;

    /* unpack */
    prev = chain_unpack_line (convert, i);
    /* upsample chroma */
    prev = chain_upsample (convert, prev, i);
    /* convert to gamma decoded RGB */
    prev = chain_convert_to_RGB (convert, prev, i);
    /* do all downscaling */
    prev = chain_scale (convert, prev, FALSE, i);
    /* do conversion between color spaces */
    prev = chain_convert (convert, prev, i);
    /* do alpha channels */
    prev = chain_alpha (convert, prev, i);
    /* do all remaining (up)scaling */
    prev = chain_scale (convert, prev, TRUE, i);
    /* convert to gamma encoded Y'Cb'Cr' */
    prev = chain_convert_to_YUV (convert, prev, i);
    /* downsample chroma */
    prev = chain_downsample (convert, prev, i);
    /* dither */
    prev = chain_dither (convert, prev, i);
    /* pack into final format */
    convert->pack_lines[i] = chain_pack (convert, prev, i);
  }

  setup_borderline (convert);
  /* now figure out allocators */
  setup_allocators (convert);

done:
  return convert;

  /* ERRORS */
no_unpack_func:
  {
    GST_ERROR ("no unpack_func for format %s",
        gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (in_info)));
    gst_video_converter_free (convert);
    return NULL;
  }
no_pack_func:
  {
    GST_ERROR ("no pack_func for format %s",
        gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (out_info)));
    gst_video_converter_free (convert);
    return NULL;
  }
}

static void
clear_matrix_data (MatrixData * data)
{
  g_free (data->t_r);
  g_free (data->t_g);
  g_free (data->t_b);
}

/**
 * gst_video_converter_free:
 * @convert: a #GstVideoConverter
 *
 * Free @convert
 *
 * Since: 1.6
 */
void
gst_video_converter_free (GstVideoConverter * convert)
{
  guint i, j;

  g_return_if_fail (convert != NULL);

  for (i = 0; i < convert->conversion_runner->n_threads; i++) {
    if (convert->upsample_p && convert->upsample_p[i])
      gst_video_chroma_resample_free (convert->upsample_p[i]);
    if (convert->upsample_i && convert->upsample_i[i])
      gst_video_chroma_resample_free (convert->upsample_i[i]);
    if (convert->downsample_p && convert->downsample_p[i])
      gst_video_chroma_resample_free (convert->downsample_p[i]);
    if (convert->downsample_i && convert->downsample_i[i])
      gst_video_chroma_resample_free (convert->downsample_i[i]);
    if (convert->v_scaler_p && convert->v_scaler_p[i])
      gst_video_scaler_free (convert->v_scaler_p[i]);
    if (convert->v_scaler_i && convert->v_scaler_i[i])
      gst_video_scaler_free (convert->v_scaler_i[i]);
    if (convert->h_scaler && convert->h_scaler[i])
      gst_video_scaler_free (convert->h_scaler[i]);
    if (convert->unpack_lines && convert->unpack_lines[i])
      gst_line_cache_free (convert->unpack_lines[i]);
    if (convert->upsample_lines && convert->upsample_lines[i])
      gst_line_cache_free (convert->upsample_lines[i]);
    if (convert->to_RGB_lines && convert->to_RGB_lines[i])
      gst_line_cache_free (convert->to_RGB_lines[i]);
    if (convert->hscale_lines && convert->hscale_lines[i])
      gst_line_cache_free (convert->hscale_lines[i]);
    if (convert->vscale_lines && convert->vscale_lines[i])
      gst_line_cache_free (convert->vscale_lines[i]);
    if (convert->convert_lines && convert->convert_lines[i])
      gst_line_cache_free (convert->convert_lines[i]);
    if (convert->alpha_lines && convert->alpha_lines[i])
      gst_line_cache_free (convert->alpha_lines[i]);
    if (convert->to_YUV_lines && convert->to_YUV_lines[i])
      gst_line_cache_free (convert->to_YUV_lines[i]);
    if (convert->downsample_lines && convert->downsample_lines[i])
      gst_line_cache_free (convert->downsample_lines[i]);
    if (convert->dither_lines && convert->dither_lines[i])
      gst_line_cache_free (convert->dither_lines[i]);
    if (convert->dither && convert->dither[i])
      gst_video_dither_free (convert->dither[i]);
  }
  g_free (convert->upsample_p);
  g_free (convert->upsample_i);
  g_free (convert->downsample_p);
  g_free (convert->downsample_i);
  g_free (convert->v_scaler_p);
  g_free (convert->v_scaler_i);
  g_free (convert->h_scaler);
  g_free (convert->unpack_lines);
  g_free (convert->pack_lines);
  g_free (convert->upsample_lines);
  g_free (convert->to_RGB_lines);
  g_free (convert->hscale_lines);
  g_free (convert->vscale_lines);
  g_free (convert->convert_lines);
  g_free (convert->alpha_lines);
  g_free (convert->to_YUV_lines);
  g_free (convert->downsample_lines);
  g_free (convert->dither_lines);
  g_free (convert->dither);

  g_free (convert->gamma_dec.gamma_table);
  g_free (convert->gamma_enc.gamma_table);

  if (convert->tmpline) {
    for (i = 0; i < convert->conversion_runner->n_threads; i++)
      g_free (convert->tmpline[i]);
    g_free (convert->tmpline);
  }

  g_free (convert->borderline);

  if (convert->config)
    gst_structure_free (convert->config);

  for (i = 0; i < 4; i++) {
    for (j = 0; j < convert->conversion_runner->n_threads; j++) {
      if (convert->fv_scaler[i].scaler)
        gst_video_scaler_free (convert->fv_scaler[i].scaler[j]);
      if (convert->fh_scaler[i].scaler)
        gst_video_scaler_free (convert->fh_scaler[i].scaler[j]);
    }
    g_free (convert->fv_scaler[i].scaler);
    g_free (convert->fh_scaler[i].scaler);
  }

  if (convert->conversion_runner)
    gst_parallelized_task_runner_free (convert->conversion_runner);

  clear_matrix_data (&convert->to_RGB_matrix);
  clear_matrix_data (&convert->convert_matrix);
  clear_matrix_data (&convert->to_YUV_matrix);

  g_slice_free (GstVideoConverter, convert);
}

static gboolean
copy_config (GQuark field_id, const GValue * value, gpointer user_data)
{
  GstVideoConverter *convert = user_data;

  gst_structure_id_set_value (convert->config, field_id, value);

  return TRUE;
}

/**
 * gst_video_converter_set_config:
 * @convert: a #GstVideoConverter
 * @config: (transfer full): a #GstStructure
 *
 * Set @config as extra configuraion for @convert.
 *
 * If the parameters in @config can not be set exactly, this function returns
 * %FALSE and will try to update as much state as possible. The new state can
 * then be retrieved and refined with gst_video_converter_get_config().
 *
 * Look at the #GST_VIDEO_CONVERTER_OPT_* fields to check valid configuration
 * option and values.
 *
 * Returns: %TRUE when @config could be set.
 *
 * Since: 1.6
 */
gboolean
gst_video_converter_set_config (GstVideoConverter * convert,
    GstStructure * config)
{
  g_return_val_if_fail (convert != NULL, FALSE);
  g_return_val_if_fail (config != NULL, FALSE);

  gst_structure_foreach (config, copy_config, convert);
  gst_structure_free (config);

  return TRUE;
}

/**
 * gst_video_converter_get_config:
 * @convert: a #GstVideoConverter
 *
 * Get the current configuration of @convert.
 *
 * Returns: a #GstStructure that remains valid for as long as @convert is valid
 *   or until gst_video_converter_set_config() is called.
 */
const GstStructure *
gst_video_converter_get_config (GstVideoConverter * convert)
{
  g_return_val_if_fail (convert != NULL, NULL);

  return convert->config;
}

/**
 * gst_video_converter_frame:
 * @convert: a #GstVideoConverter
 * @dest: a #GstVideoFrame
 * @src: a #GstVideoFrame
 *
 * Convert the pixels of @src into @dest using @convert.
 *
 * Since: 1.6
 */
void
gst_video_converter_frame (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest)
{
  g_return_if_fail (convert != NULL);
  g_return_if_fail (src != NULL);
  g_return_if_fail (dest != NULL);

  convert->convert (convert, src, dest);
}

static void
video_converter_compute_matrix (GstVideoConverter * convert)
{
  MatrixData *dst = &convert->convert_matrix;

  color_matrix_set_identity (dst);
  compute_matrix_to_RGB (convert, dst);
  compute_matrix_to_YUV (convert, dst, FALSE);

  convert->current_bits = 8;
  prepare_matrix (convert, dst);
}

static void
video_converter_compute_resample (GstVideoConverter * convert, gint idx)
{
  GstVideoInfo *in_info, *out_info;
  const GstVideoFormatInfo *sfinfo, *dfinfo;

  if (CHECK_CHROMA_NONE (convert))
    return;

  in_info = &convert->in_info;
  out_info = &convert->out_info;

  sfinfo = in_info->finfo;
  dfinfo = out_info->finfo;

  GST_DEBUG ("site: %d->%d, w_sub: %d->%d, h_sub: %d->%d", in_info->chroma_site,
      out_info->chroma_site, sfinfo->w_sub[2], dfinfo->w_sub[2],
      sfinfo->h_sub[2], dfinfo->h_sub[2]);

  if (sfinfo->w_sub[2] != dfinfo->w_sub[2] ||
      sfinfo->h_sub[2] != dfinfo->h_sub[2] ||
      in_info->chroma_site != out_info->chroma_site ||
      in_info->width != out_info->width ||
      in_info->height != out_info->height) {
    if (GST_VIDEO_INFO_IS_INTERLACED (in_info)) {
      if (!CHECK_CHROMA_DOWNSAMPLE (convert))
        convert->upsample_i[idx] = gst_video_chroma_resample_new (0,
            in_info->chroma_site, GST_VIDEO_CHROMA_FLAG_INTERLACED,
            sfinfo->unpack_format, sfinfo->w_sub[2], sfinfo->h_sub[2]);
      if (!CHECK_CHROMA_UPSAMPLE (convert))
        convert->downsample_i[idx] =
            gst_video_chroma_resample_new (0, out_info->chroma_site,
            GST_VIDEO_CHROMA_FLAG_INTERLACED, dfinfo->unpack_format,
            -dfinfo->w_sub[2], -dfinfo->h_sub[2]);
    }
    if (!CHECK_CHROMA_DOWNSAMPLE (convert))
      convert->upsample_p[idx] = gst_video_chroma_resample_new (0,
          in_info->chroma_site, 0, sfinfo->unpack_format, sfinfo->w_sub[2],
          sfinfo->h_sub[2]);
    if (!CHECK_CHROMA_UPSAMPLE (convert))
      convert->downsample_p[idx] = gst_video_chroma_resample_new (0,
          out_info->chroma_site, 0, dfinfo->unpack_format, -dfinfo->w_sub[2],
          -dfinfo->h_sub[2]);
  }
}

#define FRAME_GET_PLANE_STRIDE(frame, plane) \
  GST_VIDEO_FRAME_PLANE_STRIDE (frame, plane)
#define FRAME_GET_PLANE_LINE(frame, plane, line) \
  (gpointer)(((guint8*)(GST_VIDEO_FRAME_PLANE_DATA (frame, plane))) + \
      FRAME_GET_PLANE_STRIDE (frame, plane) * (line))

#define FRAME_GET_COMP_STRIDE(frame, comp) \
  GST_VIDEO_FRAME_COMP_STRIDE (frame, comp)
#define FRAME_GET_COMP_LINE(frame, comp, line) \
  (gpointer)(((guint8*)(GST_VIDEO_FRAME_COMP_DATA (frame, comp))) + \
      FRAME_GET_COMP_STRIDE (frame, comp) * (line))

#define FRAME_GET_STRIDE(frame)      FRAME_GET_PLANE_STRIDE (frame, 0)
#define FRAME_GET_LINE(frame,line)   FRAME_GET_PLANE_LINE (frame, 0, line)

#define FRAME_GET_Y_LINE(frame,line) FRAME_GET_COMP_LINE(frame, GST_VIDEO_COMP_Y, line)
#define FRAME_GET_U_LINE(frame,line) FRAME_GET_COMP_LINE(frame, GST_VIDEO_COMP_U, line)
#define FRAME_GET_V_LINE(frame,line) FRAME_GET_COMP_LINE(frame, GST_VIDEO_COMP_V, line)
#define FRAME_GET_A_LINE(frame,line) FRAME_GET_COMP_LINE(frame, GST_VIDEO_COMP_A, line)

#define FRAME_GET_Y_STRIDE(frame)    FRAME_GET_COMP_STRIDE(frame, GST_VIDEO_COMP_Y)
#define FRAME_GET_U_STRIDE(frame)    FRAME_GET_COMP_STRIDE(frame, GST_VIDEO_COMP_U)
#define FRAME_GET_V_STRIDE(frame)    FRAME_GET_COMP_STRIDE(frame, GST_VIDEO_COMP_V)
#define FRAME_GET_A_STRIDE(frame)    FRAME_GET_COMP_STRIDE(frame, GST_VIDEO_COMP_A)


#define UNPACK_FRAME(frame,dest,line,x,width)        \
  frame->info.finfo->unpack_func (frame->info.finfo, \
      (GST_VIDEO_FRAME_IS_INTERLACED (frame) ?       \
        GST_VIDEO_PACK_FLAG_INTERLACED :             \
        GST_VIDEO_PACK_FLAG_NONE),                   \
      dest, frame->data, frame->info.stride, x,      \
      line, width)
#define PACK_FRAME(frame,src,line,width)             \
  frame->info.finfo->pack_func (frame->info.finfo,   \
      (GST_VIDEO_FRAME_IS_INTERLACED (frame) ?       \
        GST_VIDEO_PACK_FLAG_INTERLACED :             \
        GST_VIDEO_PACK_FLAG_NONE),                   \
      src, 0, frame->data, frame->info.stride,       \
      frame->info.chroma_site, line, width);

static gpointer
get_dest_line (GstLineCache * cache, gint idx, gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  guint8 *line;
  gint pstride = convert->pack_pstride;
  gint out_x = convert->out_x;
  guint cline;

  cline = CLAMP (idx, 0, convert->out_maxheight - 1);

  line = FRAME_GET_LINE (convert->dest, cline);
  GST_DEBUG ("get dest line %d %p", cline, line);

  if (convert->borderline) {
    gint r_border = (out_x + convert->out_width) * pstride;
    gint rb_width = convert->out_maxwidth * pstride - r_border;
    gint lb_width = out_x * pstride;

    memcpy (line, convert->borderline, lb_width);
    memcpy (line + r_border, convert->borderline, rb_width);
  }
  line += out_x * pstride;

  return line;
}

static gboolean
do_unpack_lines (GstLineCache * cache, gint idx, gint out_line, gint in_line,
    gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  gpointer tmpline;
  guint cline;

  cline = CLAMP (in_line + convert->in_y, 0, convert->in_maxheight - 1);

  if (cache->alloc_writable || !convert->identity_unpack) {
    tmpline = gst_line_cache_alloc_line (cache, out_line);
    GST_DEBUG ("unpack line %d (%u) %p", in_line, cline, tmpline);
    UNPACK_FRAME (convert->src, tmpline, cline, convert->in_x,
        convert->in_width);
  } else {
    tmpline = ((guint8 *) FRAME_GET_LINE (convert->src, cline)) +
        convert->in_x * convert->unpack_pstride;
    GST_DEBUG ("get src line %d (%u) %p", in_line, cline, tmpline);
  }
  gst_line_cache_add_line (cache, in_line, tmpline);

  return TRUE;
}

static gboolean
do_upsample_lines (GstLineCache * cache, gint idx, gint out_line, gint in_line,
    gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  gpointer *lines;
  gint i, start_line, n_lines;

  n_lines = convert->up_n_lines;
  start_line = in_line;
  if (start_line < n_lines + convert->up_offset) {
    start_line += convert->up_offset;
    out_line += convert->up_offset;
  }

  /* get the lines needed for chroma upsample */
  lines =
      gst_line_cache_get_lines (cache->prev, idx, out_line, start_line,
      n_lines);

  if (convert->upsample) {
    GST_DEBUG ("doing upsample %d-%d %p", start_line, start_line + n_lines - 1,
        lines[0]);
    gst_video_chroma_resample (convert->upsample[idx], lines,
        convert->in_width);
  }

  for (i = 0; i < n_lines; i++)
    gst_line_cache_add_line (cache, start_line + i, lines[i]);

  return TRUE;
}

static gboolean
do_convert_to_RGB_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  MatrixData *data = &convert->to_RGB_matrix;
  gpointer *lines, destline;

  lines = gst_line_cache_get_lines (cache->prev, idx, out_line, in_line, 1);
  destline = lines[0];

  if (data->matrix_func) {
    GST_DEBUG ("to RGB line %d %p", in_line, destline);
    data->matrix_func (data, destline);
  }
  if (convert->gamma_dec.gamma_func) {
    destline = gst_line_cache_alloc_line (cache, out_line);

    GST_DEBUG ("gamma decode line %d %p->%p", in_line, lines[0], destline);
    convert->gamma_dec.gamma_func (&convert->gamma_dec, destline, lines[0]);
  }
  gst_line_cache_add_line (cache, in_line, destline);

  return TRUE;
}

static gboolean
do_hscale_lines (GstLineCache * cache, gint idx, gint out_line, gint in_line,
    gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  gpointer *lines, destline;

  lines = gst_line_cache_get_lines (cache->prev, idx, out_line, in_line, 1);

  destline = gst_line_cache_alloc_line (cache, out_line);

  GST_DEBUG ("hresample line %d %p->%p", in_line, lines[0], destline);
  gst_video_scaler_horizontal (convert->h_scaler[idx], convert->h_scale_format,
      lines[0], destline, 0, convert->out_width);

  gst_line_cache_add_line (cache, in_line, destline);

  return TRUE;
}

static gboolean
do_vscale_lines (GstLineCache * cache, gint idx, gint out_line, gint in_line,
    gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  gpointer *lines, destline;
  guint sline, n_lines;
  guint cline;

  cline = CLAMP (in_line, 0, convert->out_height - 1);

  gst_video_scaler_get_coeff (convert->v_scaler[idx], cline, &sline, &n_lines);
  lines = gst_line_cache_get_lines (cache->prev, idx, out_line, sline, n_lines);

  destline = gst_line_cache_alloc_line (cache, out_line);

  GST_DEBUG ("vresample line %d %d-%d %p->%p", in_line, sline,
      sline + n_lines - 1, lines[0], destline);
  gst_video_scaler_vertical (convert->v_scaler[idx], convert->v_scale_format,
      lines, destline, cline, convert->v_scale_width);

  gst_line_cache_add_line (cache, in_line, destline);

  return TRUE;
}

static gboolean
do_convert_lines (GstLineCache * cache, gint idx, gint out_line, gint in_line,
    gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  MatrixData *data = &convert->convert_matrix;
  gpointer *lines, destline;
  guint in_bits, out_bits;
  gint width;

  lines = gst_line_cache_get_lines (cache->prev, idx, out_line, in_line, 1);

  destline = lines[0];

  in_bits = convert->in_bits;
  out_bits = convert->out_bits;

  width = MIN (convert->in_width, convert->out_width);

  if (out_bits == 16 || in_bits == 16) {
    gpointer srcline = lines[0];

    if (out_bits != in_bits)
      destline = gst_line_cache_alloc_line (cache, out_line);

    /* FIXME, we can scale in the conversion matrix */
    if (in_bits == 8) {
      GST_DEBUG ("8->16 line %d %p->%p", in_line, srcline, destline);
      video_orc_convert_u8_to_u16 (destline, srcline, width * 4);
      srcline = destline;
    }

    if (data->matrix_func) {
      GST_DEBUG ("matrix line %d %p", in_line, srcline);
      data->matrix_func (data, srcline);
    }

    /* FIXME, dither here */
    if (out_bits == 8) {
      GST_DEBUG ("16->8 line %d %p->%p", in_line, srcline, destline);
      video_orc_convert_u16_to_u8 (destline, srcline, width * 4);
    }
  } else {
    if (data->matrix_func) {
      GST_DEBUG ("matrix line %d %p", in_line, destline);
      data->matrix_func (data, destline);
    }
  }
  gst_line_cache_add_line (cache, in_line, destline);

  return TRUE;
}

static gboolean
do_alpha_lines (GstLineCache * cache, gint idx, gint out_line, gint in_line,
    gpointer user_data)
{
  gpointer *lines, destline;
  GstVideoConverter *convert = user_data;
  gint width = MIN (convert->in_width, convert->out_width);

  lines = gst_line_cache_get_lines (cache->prev, idx, out_line, in_line, 1);
  destline = lines[0];

  GST_DEBUG ("alpha line %d %p", in_line, destline);
  convert->alpha_func (convert, destline, width);

  gst_line_cache_add_line (cache, in_line, destline);

  return TRUE;
}

static gboolean
do_convert_to_YUV_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  MatrixData *data = &convert->to_YUV_matrix;
  gpointer *lines, destline;

  lines = gst_line_cache_get_lines (cache->prev, idx, out_line, in_line, 1);
  destline = lines[0];

  if (convert->gamma_enc.gamma_func) {
    destline = gst_line_cache_alloc_line (cache, out_line);

    GST_DEBUG ("gamma encode line %d %p->%p", in_line, lines[0], destline);
    convert->gamma_enc.gamma_func (&convert->gamma_enc, destline, lines[0]);
  }
  if (data->matrix_func) {
    GST_DEBUG ("to YUV line %d %p", in_line, destline);
    data->matrix_func (data, destline);
  }
  gst_line_cache_add_line (cache, in_line, destline);

  return TRUE;
}

static gboolean
do_downsample_lines (GstLineCache * cache, gint idx, gint out_line,
    gint in_line, gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  gpointer *lines;
  gint i, start_line, n_lines;

  n_lines = convert->down_n_lines;
  start_line = in_line;
  if (start_line < n_lines + convert->down_offset)
    start_line += convert->down_offset;

  /* get the lines needed for chroma downsample */
  lines =
      gst_line_cache_get_lines (cache->prev, idx, out_line, start_line,
      n_lines);

  if (convert->downsample) {
    GST_DEBUG ("downsample line %d %d-%d %p", in_line, start_line,
        start_line + n_lines - 1, lines[0]);
    gst_video_chroma_resample (convert->downsample[idx], lines,
        convert->out_width);
  }

  for (i = 0; i < n_lines; i++)
    gst_line_cache_add_line (cache, start_line + i, lines[i]);

  return TRUE;
}

static gboolean
do_dither_lines (GstLineCache * cache, gint idx, gint out_line, gint in_line,
    gpointer user_data)
{
  GstVideoConverter *convert = user_data;
  gpointer *lines, destline;

  lines = gst_line_cache_get_lines (cache->prev, idx, out_line, in_line, 1);
  destline = lines[0];

  if (convert->dither) {
    GST_DEBUG ("Dither line %d %p", in_line, destline);
    gst_video_dither_line (convert->dither[idx], destline, 0, out_line,
        convert->out_width);
  }
  gst_line_cache_add_line (cache, in_line, destline);

  return TRUE;
}

typedef struct
{
  GstLineCache *pack_lines;
  gint idx;
  gint h_0, h_1;
  gint pack_lines_count;
  gint out_y;
  gboolean identity_pack;
  gint lb_width, out_maxwidth;
  GstVideoFrame *dest;
} ConvertTask;

static void
convert_generic_task (ConvertTask * task)
{
  gint i;

  for (i = task->h_0; i < task->h_1; i += task->pack_lines_count) {
    gpointer *lines;

    /* load the lines needed to pack */
    lines =
        gst_line_cache_get_lines (task->pack_lines, task->idx, i + task->out_y,
        i, task->pack_lines_count);

    if (!task->identity_pack) {
      /* take away the border */
      guint8 *l = ((guint8 *) lines[0]) - task->lb_width;
      /* and pack into destination */
      GST_DEBUG ("pack line %d %p (%p)", i + task->out_y, lines[0], l);
      PACK_FRAME (task->dest, l, i + task->out_y, task->out_maxwidth);
    }
  }
}

static void
video_converter_generic (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint i;
  gint out_maxwidth, out_maxheight;
  gint out_x, out_y, out_height;
  gint pack_lines, pstride;
  gint lb_width;
  ConvertTask *tasks;
  ConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  out_height = convert->out_height;
  out_maxwidth = convert->out_maxwidth;
  out_maxheight = convert->out_maxheight;

  out_x = convert->out_x;
  out_y = convert->out_y;

  convert->src = src;
  convert->dest = dest;

  if (GST_VIDEO_FRAME_IS_INTERLACED (src)) {
    GST_DEBUG ("setup interlaced frame");
    convert->upsample = convert->upsample_i;
    convert->downsample = convert->downsample_i;
    convert->v_scaler = convert->v_scaler_i;
  } else {
    GST_DEBUG ("setup progressive frame");
    convert->upsample = convert->upsample_p;
    convert->downsample = convert->downsample_p;
    convert->v_scaler = convert->v_scaler_p;
  }
  if (convert->upsample[0]) {
    gst_video_chroma_resample_get_info (convert->upsample[0],
        &convert->up_n_lines, &convert->up_offset);
  } else {
    convert->up_n_lines = 1;
    convert->up_offset = 0;
  }
  if (convert->downsample[0]) {
    gst_video_chroma_resample_get_info (convert->downsample[0],
        &convert->down_n_lines, &convert->down_offset);
  } else {
    convert->down_n_lines = 1;
    convert->down_offset = 0;
  }

  pack_lines = convert->pack_nlines;    /* only 1 for now */
  pstride = convert->pack_pstride;

  lb_width = out_x * pstride;

  if (convert->borderline) {
    /* FIXME we should try to avoid PACK_FRAME */
    for (i = 0; i < out_y; i++)
      PACK_FRAME (dest, convert->borderline, i, out_maxwidth);
  }

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (ConvertTask, n_threads);
  tasks_p = g_newa (ConvertTask *, n_threads);

  lines_per_thread =
      GST_ROUND_UP_N ((out_height + n_threads - 1) / n_threads, pack_lines);

  for (i = 0; i < n_threads; i++) {
    tasks[i].dest = dest;
    tasks[i].pack_lines = convert->pack_lines[i];
    tasks[i].idx = i;
    tasks[i].pack_lines_count = pack_lines;
    tasks[i].out_y = out_y;
    tasks[i].identity_pack = convert->identity_pack;
    tasks[i].lb_width = lb_width;
    tasks[i].out_maxwidth = out_maxwidth;

    tasks[i].h_0 = i * lines_per_thread;
    tasks[i].h_1 = MIN ((i + 1) * lines_per_thread, out_height);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_generic_task, (gpointer) tasks_p);

  if (convert->borderline) {
    for (i = out_y + out_height; i < out_maxheight; i++)
      PACK_FRAME (dest, convert->borderline, i, out_maxwidth);
  }
  if (convert->pack_pal) {
    memcpy (GST_VIDEO_FRAME_PLANE_DATA (dest, 1), convert->pack_pal,
        convert->pack_palsize);
  }
}

static void convert_fill_border (GstVideoConverter * convert,
    GstVideoFrame * dest);

/* Fast paths */

#define GET_LINE_OFFSETS(interlaced,line,l1,l2) \
    if (interlaced) {                           \
      l1 = (line & 2 ? line - 1 : line);        \
      l2 = l1 + 2;                              \
    } else {                                    \
      l1 = line;                                \
      l2 = l1 + 1;                              \
    }

typedef struct
{
  const GstVideoFrame *src;
  GstVideoFrame *dest;
  gint height_0, height_1;

  /* parameters */
  gboolean interlaced;
  gint width;
  gint alpha;
  MatrixData *data;
  gint in_x, in_y;
  gint out_x, out_y;
  gpointer tmpline;
} FConvertTask;

static void
convert_I420_YUY2_task (FConvertTask * task)
{
  gint i;
  gint l1, l2;

  for (i = task->height_0; i < task->height_1; i += 2) {
    GET_LINE_OFFSETS (task->interlaced, i, l1, l2);

    video_orc_convert_I420_YUY2 (FRAME_GET_LINE (task->dest, l1),
        FRAME_GET_LINE (task->dest, l2),
        FRAME_GET_Y_LINE (task->src, l1),
        FRAME_GET_Y_LINE (task->src, l2),
        FRAME_GET_U_LINE (task->src, i >> 1),
        FRAME_GET_V_LINE (task->src, i >> 1), (task->width + 1) / 2);
  }
}

static void
convert_I420_YUY2 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  int i;
  gint width = convert->in_width;
  gint height = convert->in_height;
  gboolean interlaced = GST_VIDEO_FRAME_IS_INTERLACED (src);
  gint h2;
  FConvertTask *tasks;
  FConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  /* I420 has half as many chroma lines, as such we have to
   * always merge two into one. For non-interlaced these are
   * the two next to each other, for interlaced one is skipped
   * in between. */
  if (interlaced)
    h2 = GST_ROUND_DOWN_4 (height);
  else
    h2 = GST_ROUND_DOWN_2 (height);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertTask, n_threads);
  tasks_p = g_newa (FConvertTask *, n_threads);

  lines_per_thread = GST_ROUND_UP_2 ((h2 + n_threads - 1) / n_threads);

  for (i = 0; i < n_threads; i++) {
    tasks[i].src = src;
    tasks[i].dest = dest;

    tasks[i].interlaced = interlaced;
    tasks[i].width = width;

    tasks[i].height_0 = i * lines_per_thread;
    tasks[i].height_1 = tasks[i].height_0 + lines_per_thread;
    tasks[i].height_1 = MIN (h2, tasks[i].height_1);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_I420_YUY2_task, (gpointer) tasks_p);

  /* now handle last lines. For interlaced these are up to 3 */
  if (h2 != height) {
    for (i = h2; i < height; i++) {
      UNPACK_FRAME (src, convert->tmpline[0], i, convert->in_x, width);
      PACK_FRAME (dest, convert->tmpline[0], i, width);
    }
  }
}

static void
convert_I420_UYVY_task (FConvertTask * task)
{
  gint i;
  gint l1, l2;

  for (i = task->height_0; i < task->height_1; i += 2) {
    GET_LINE_OFFSETS (task->interlaced, i, l1, l2);

    video_orc_convert_I420_UYVY (FRAME_GET_LINE (task->dest, l1),
        FRAME_GET_LINE (task->dest, l2),
        FRAME_GET_Y_LINE (task->src, l1),
        FRAME_GET_Y_LINE (task->src, l2),
        FRAME_GET_U_LINE (task->src, i >> 1),
        FRAME_GET_V_LINE (task->src, i >> 1), (task->width + 1) / 2);
  }
}

static void
convert_I420_UYVY (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  int i;
  gint width = convert->in_width;
  gint height = convert->in_height;
  gboolean interlaced = GST_VIDEO_FRAME_IS_INTERLACED (src);
  gint h2;
  FConvertTask *tasks;
  FConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  /* I420 has half as many chroma lines, as such we have to
   * always merge two into one. For non-interlaced these are
   * the two next to each other, for interlaced one is skipped
   * in between. */
  if (interlaced)
    h2 = GST_ROUND_DOWN_4 (height);
  else
    h2 = GST_ROUND_DOWN_2 (height);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertTask, n_threads);
  tasks_p = g_newa (FConvertTask *, n_threads);

  lines_per_thread = GST_ROUND_UP_2 ((h2 + n_threads - 1) / n_threads);

  for (i = 0; i < n_threads; i++) {
    tasks[i].src = src;
    tasks[i].dest = dest;

    tasks[i].interlaced = interlaced;
    tasks[i].width = width;

    tasks[i].height_0 = i * lines_per_thread;
    tasks[i].height_1 = tasks[i].height_0 + lines_per_thread;
    tasks[i].height_1 = MIN (h2, tasks[i].height_1);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_I420_UYVY_task, (gpointer) tasks_p);

  /* now handle last lines. For interlaced these are up to 3 */
  if (h2 != height) {
    for (i = h2; i < height; i++) {
      UNPACK_FRAME (src, convert->tmpline[0], i, convert->in_x, width);
      PACK_FRAME (dest, convert->tmpline[0], i, width);
    }
  }
}

static void
convert_I420_AYUV_task (FConvertTask * task)
{
  gint i;
  gint l1, l2;

  for (i = task->height_0; i < task->height_1; i += 2) {
    GET_LINE_OFFSETS (task->interlaced, i, l1, l2);

    video_orc_convert_I420_AYUV (FRAME_GET_LINE (task->dest, l1),
        FRAME_GET_LINE (task->dest, l2),
        FRAME_GET_Y_LINE (task->src, l1),
        FRAME_GET_Y_LINE (task->src, l2),
        FRAME_GET_U_LINE (task->src, i >> 1), FRAME_GET_V_LINE (task->src,
            i >> 1), task->alpha, task->width);
  }
}

static void
convert_I420_AYUV (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  int i;
  gint width = convert->in_width;
  gint height = convert->in_height;
  gboolean interlaced = GST_VIDEO_FRAME_IS_INTERLACED (src);
  guint8 alpha = MIN (convert->alpha_value, 255);
  gint h2;
  FConvertTask *tasks;
  FConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  /* I420 has half as many chroma lines, as such we have to
   * always merge two into one. For non-interlaced these are
   * the two next to each other, for interlaced one is skipped
   * in between. */
  if (interlaced)
    h2 = GST_ROUND_DOWN_4 (height);
  else
    h2 = GST_ROUND_DOWN_2 (height);


  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertTask, n_threads);
  tasks_p = g_newa (FConvertTask *, n_threads);

  lines_per_thread = GST_ROUND_UP_2 ((h2 + n_threads - 1) / n_threads);

  for (i = 0; i < n_threads; i++) {
    tasks[i].src = src;
    tasks[i].dest = dest;

    tasks[i].interlaced = interlaced;
    tasks[i].width = width;
    tasks[i].alpha = alpha;

    tasks[i].height_0 = i * lines_per_thread;
    tasks[i].height_1 = tasks[i].height_0 + lines_per_thread;
    tasks[i].height_1 = MIN (h2, tasks[i].height_1);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_I420_AYUV_task, (gpointer) tasks_p);

  /* now handle last lines. For interlaced these are up to 3 */
  if (h2 != height) {
    for (i = h2; i < height; i++) {
      UNPACK_FRAME (src, convert->tmpline[0], i, convert->in_x, width);
      if (alpha != 0xff)
        convert_set_alpha_u8 (convert, convert->tmpline[0], width);
      PACK_FRAME (dest, convert->tmpline[0], i, width);
    }
  }
}

static void
convert_YUY2_I420_task (FConvertTask * task)
{
  gint i;
  gint l1, l2;

  for (i = task->height_0; i < task->height_1; i += 2) {
    GET_LINE_OFFSETS (task->interlaced, i, l1, l2);

    video_orc_convert_YUY2_I420 (FRAME_GET_Y_LINE (task->dest, l1),
        FRAME_GET_Y_LINE (task->dest, l2),
        FRAME_GET_U_LINE (task->dest, i >> 1),
        FRAME_GET_V_LINE (task->dest, i >> 1),
        FRAME_GET_LINE (task->src, l1), FRAME_GET_LINE (task->src, l2),
        (task->width + 1) / 2);
  }
}

static void
convert_YUY2_I420 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  int i;
  gint width = convert->in_width;
  gint height = convert->in_height;
  gboolean interlaced = GST_VIDEO_FRAME_IS_INTERLACED (src);
  gint h2;
  FConvertTask *tasks;
  FConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  /* I420 has half as many chroma lines, as such we have to
   * always merge two into one. For non-interlaced these are
   * the two next to each other, for interlaced one is skipped
   * in between. */
  if (interlaced)
    h2 = GST_ROUND_DOWN_4 (height);
  else
    h2 = GST_ROUND_DOWN_2 (height);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertTask, n_threads);
  tasks_p = g_newa (FConvertTask *, n_threads);

  lines_per_thread = GST_ROUND_UP_2 ((h2 + n_threads - 1) / n_threads);

  for (i = 0; i < n_threads; i++) {
    tasks[i].src = src;
    tasks[i].dest = dest;

    tasks[i].interlaced = interlaced;
    tasks[i].width = width;

    tasks[i].height_0 = i * lines_per_thread;
    tasks[i].height_1 = tasks[i].height_0 + lines_per_thread;
    tasks[i].height_1 = MIN (h2, tasks[i].height_1);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_YUY2_I420_task, (gpointer) tasks_p);

  /* now handle last lines. For interlaced these are up to 3 */
  if (h2 != height) {
    for (i = h2; i < height; i++) {
      UNPACK_FRAME (src, convert->tmpline[0], i, convert->in_x, width);
      PACK_FRAME (dest, convert->tmpline[0], i, width);
    }
  }
}

typedef struct
{
  const guint8 *s, *s2, *su, *sv;
  guint8 *d, *d2, *du, *dv;
  gint sstride, sustride, svstride;
  gint dstride, dustride, dvstride;
  gint width, height;
  gint alpha;
  MatrixData *data;
} FConvertPlaneTask;

static void
convert_YUY2_AYUV_task (FConvertPlaneTask * task)
{
  video_orc_convert_YUY2_AYUV (task->d, task->dstride, task->s,
      task->sstride, task->alpha, (task->width + 1) / 2, task->height);
}

static void
convert_YUY2_AYUV (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *d;
  guint8 alpha = MIN (convert->alpha_value, 255);
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (GST_ROUND_UP_2 (convert->in_x) * 2);
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (convert->out_x * 4);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].alpha = alpha;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_YUY2_AYUV_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_YUY2_Y42B_task (FConvertPlaneTask * task)
{
  video_orc_convert_YUY2_Y42B (task->d, task->dstride, task->du,
      task->dustride, task->dv, task->dvstride,
      task->s, task->sstride, (task->width + 1) / 2, task->height);
}

static void
convert_YUY2_Y42B (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *dy, *du, *dv;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (GST_ROUND_UP_2 (convert->in_x) * 2);

  dy = FRAME_GET_Y_LINE (dest, convert->out_y);
  dy += convert->out_x;
  du = FRAME_GET_U_LINE (dest, convert->out_y);
  du += convert->out_x >> 1;
  dv = FRAME_GET_V_LINE (dest, convert->out_y);
  dv += convert->out_x >> 1;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_Y_STRIDE (dest);
    tasks[i].dustride = FRAME_GET_U_STRIDE (dest);
    tasks[i].dvstride = FRAME_GET_V_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = dy + i * lines_per_thread * tasks[i].dstride;
    tasks[i].du = du + i * lines_per_thread * tasks[i].dustride;
    tasks[i].dv = dv + i * lines_per_thread * tasks[i].dvstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_YUY2_Y42B_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_YUY2_Y444_task (FConvertPlaneTask * task)
{
  video_orc_convert_YUY2_Y444 (task->d,
      task->dstride, task->du,
      task->dustride, task->dv,
      task->dvstride, task->s,
      task->sstride, (task->width + 1) / 2, task->height);
}

static void
convert_YUY2_Y444 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *dy, *du, *dv;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (GST_ROUND_UP_2 (convert->in_x) * 2);

  dy = FRAME_GET_Y_LINE (dest, convert->out_y);
  dy += convert->out_x;
  du = FRAME_GET_U_LINE (dest, convert->out_y);
  du += convert->out_x;
  dv = FRAME_GET_V_LINE (dest, convert->out_y);
  dv += convert->out_x;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_Y_STRIDE (dest);
    tasks[i].dustride = FRAME_GET_U_STRIDE (dest);
    tasks[i].dvstride = FRAME_GET_V_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = dy + i * lines_per_thread * tasks[i].dstride;
    tasks[i].du = du + i * lines_per_thread * tasks[i].dustride;
    tasks[i].dv = dv + i * lines_per_thread * tasks[i].dvstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_YUY2_Y444_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_UYVY_I420_task (FConvertTask * task)
{
  gint i;
  gint l1, l2;

  for (i = task->height_0; i < task->height_1; i += 2) {
    GET_LINE_OFFSETS (task->interlaced, i, l1, l2);

    video_orc_convert_UYVY_I420 (FRAME_GET_COMP_LINE (task->dest, 0, l1),
        FRAME_GET_COMP_LINE (task->dest, 0, l2),
        FRAME_GET_COMP_LINE (task->dest, 1, i >> 1),
        FRAME_GET_COMP_LINE (task->dest, 2, i >> 1),
        FRAME_GET_LINE (task->src, l1), FRAME_GET_LINE (task->src, l2),
        (task->width + 1) / 2);
  }
}

static void
convert_UYVY_I420 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  int i;
  gint width = convert->in_width;
  gint height = convert->in_height;
  gboolean interlaced = GST_VIDEO_FRAME_IS_INTERLACED (src);
  gint h2;
  FConvertTask *tasks;
  FConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  /* I420 has half as many chroma lines, as such we have to
   * always merge two into one. For non-interlaced these are
   * the two next to each other, for interlaced one is skipped
   * in between. */
  if (interlaced)
    h2 = GST_ROUND_DOWN_4 (height);
  else
    h2 = GST_ROUND_DOWN_2 (height);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertTask, n_threads);
  tasks_p = g_newa (FConvertTask *, n_threads);

  lines_per_thread = GST_ROUND_UP_2 ((h2 + n_threads - 1) / n_threads);

  for (i = 0; i < n_threads; i++) {
    tasks[i].src = src;
    tasks[i].dest = dest;

    tasks[i].interlaced = interlaced;
    tasks[i].width = width;

    tasks[i].height_0 = i * lines_per_thread;
    tasks[i].height_1 = tasks[i].height_0 + lines_per_thread;
    tasks[i].height_1 = MIN (h2, tasks[i].height_1);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_UYVY_I420_task, (gpointer) tasks_p);

  /* now handle last lines. For interlaced these are up to 3 */
  if (h2 != height) {
    for (i = h2; i < height; i++) {
      UNPACK_FRAME (src, convert->tmpline[0], i, convert->in_x, width);
      PACK_FRAME (dest, convert->tmpline[0], i, width);
    }
  }
}

static void
convert_UYVY_AYUV_task (FConvertPlaneTask * task)
{
  video_orc_convert_UYVY_AYUV (task->d, task->dstride, task->s,
      task->sstride, task->alpha, (task->width + 1) / 2, task->height);
}

static void
convert_UYVY_AYUV (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *d;
  guint8 alpha = MIN (convert->alpha_value, 255);
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (GST_ROUND_UP_2 (convert->in_x) * 2);
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (convert->out_x * 4);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].alpha = alpha;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_UYVY_AYUV_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_UYVY_YUY2_task (FConvertPlaneTask * task)
{
  video_orc_convert_UYVY_YUY2 (task->d, task->dstride, task->s,
      task->sstride, (task->width + 1) / 2, task->height);
}

static void
convert_UYVY_YUY2 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (GST_ROUND_UP_2 (convert->in_x) * 2);
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (GST_ROUND_UP_2 (convert->out_x) * 2);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_UYVY_YUY2_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_UYVY_Y42B_task (FConvertPlaneTask * task)
{
  video_orc_convert_UYVY_Y42B (task->d, task->dstride, task->du,
      task->dustride, task->dv, task->dvstride,
      task->s, task->sstride, (task->width + 1) / 2, task->height);
}

static void
convert_UYVY_Y42B (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *dy, *du, *dv;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (GST_ROUND_UP_2 (convert->in_x) * 2);

  dy = FRAME_GET_Y_LINE (dest, convert->out_y);
  dy += convert->out_x;
  du = FRAME_GET_U_LINE (dest, convert->out_y);
  du += convert->out_x >> 1;
  dv = FRAME_GET_V_LINE (dest, convert->out_y);
  dv += convert->out_x >> 1;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_Y_STRIDE (dest);
    tasks[i].dustride = FRAME_GET_U_STRIDE (dest);
    tasks[i].dvstride = FRAME_GET_V_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = dy + i * lines_per_thread * tasks[i].dstride;
    tasks[i].du = du + i * lines_per_thread * tasks[i].dustride;
    tasks[i].dv = dv + i * lines_per_thread * tasks[i].dvstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_UYVY_Y42B_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_UYVY_Y444_task (FConvertPlaneTask * task)
{
  video_orc_convert_UYVY_Y444 (task->d,
      task->dstride, task->du,
      task->dustride, task->dv,
      task->dvstride, task->s,
      task->sstride, (task->width + 1) / 2, task->height);
}

static void
convert_UYVY_Y444 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *dy, *du, *dv;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (GST_ROUND_UP_2 (convert->in_x) * 2);

  dy = FRAME_GET_Y_LINE (dest, convert->out_y);
  dy += convert->out_x;
  du = FRAME_GET_U_LINE (dest, convert->out_y);
  du += convert->out_x;
  dv = FRAME_GET_V_LINE (dest, convert->out_y);
  dv += convert->out_x;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_Y_STRIDE (dest);
    tasks[i].dustride = FRAME_GET_U_STRIDE (dest);
    tasks[i].dvstride = FRAME_GET_V_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = dy + i * lines_per_thread * tasks[i].dstride;
    tasks[i].du = du + i * lines_per_thread * tasks[i].dustride;
    tasks[i].dv = dv + i * lines_per_thread * tasks[i].dvstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_UYVY_Y444_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_UYVY_GRAY8_task (FConvertPlaneTask * task)
{
  video_orc_convert_UYVY_GRAY8 (task->d, task->dstride, (guint16 *) task->s,
      task->sstride, task->width, task->height);
}

static void
convert_UYVY_GRAY8 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s;
  guint8 *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = GST_VIDEO_FRAME_PLANE_DATA (src, 0);
  d = GST_VIDEO_FRAME_PLANE_DATA (dest, 0);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_UYVY_GRAY8_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_AYUV_I420_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_I420 (task->d,
      2 * task->dstride, task->d2,
      2 * task->dstride, task->du,
      task->dustride, task->dv,
      task->dvstride, task->s,
      2 * task->sstride, task->s2,
      2 * task->sstride, task->width / 2, task->height / 2);
}

static void
convert_AYUV_I420 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s1, *s2, *dy1, *dy2, *du, *dv;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s1 = FRAME_GET_LINE (src, convert->in_y + 0);
  s1 += convert->in_x * 4;
  s2 = FRAME_GET_LINE (src, convert->in_y + 1);
  s2 += convert->in_x * 4;

  dy1 = FRAME_GET_Y_LINE (dest, convert->out_y + 0);
  dy1 += convert->out_x;
  dy2 = FRAME_GET_Y_LINE (dest, convert->out_y + 1);
  dy2 += convert->out_x;
  du = FRAME_GET_U_LINE (dest, convert->out_y >> 1);
  du += convert->out_x >> 1;
  dv = FRAME_GET_V_LINE (dest, convert->out_y >> 1);
  dv += convert->out_x >> 1;

  /* only for even width/height */

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = GST_ROUND_UP_2 ((height + n_threads - 1) / n_threads);

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_Y_STRIDE (dest);
    tasks[i].dustride = FRAME_GET_U_STRIDE (dest);
    tasks[i].dvstride = FRAME_GET_V_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = dy1 + i * lines_per_thread * tasks[i].dstride;
    tasks[i].d2 = dy2 + i * lines_per_thread * tasks[i].dstride;
    tasks[i].du = du + i * lines_per_thread * tasks[i].dustride / 2;
    tasks[i].dv = dv + i * lines_per_thread * tasks[i].dvstride / 2;
    tasks[i].s = s1 + i * lines_per_thread * tasks[i].sstride;
    tasks[i].s2 = s2 + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_I420_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_AYUV_YUY2_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_YUY2 (task->d, task->dstride, task->s,
      task->sstride, task->width / 2, task->height);
}

static void
convert_AYUV_YUY2 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += convert->in_x * 4;
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (GST_ROUND_UP_2 (convert->out_x) * 2);

  /* only for even width */
  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_YUY2_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_AYUV_UYVY_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_UYVY (task->d, task->dstride, task->s,
      task->sstride, task->width / 2, task->height);
}

static void
convert_AYUV_UYVY (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += convert->in_x * 4;
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (GST_ROUND_UP_2 (convert->out_x) * 2);

  /* only for even width */
  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_UYVY_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_AYUV_Y42B_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_Y42B (task->d, task->dstride, task->du,
      task->dustride, task->dv, task->dvstride,
      task->s, task->sstride, task->width / 2, task->height);
}

static void
convert_AYUV_Y42B (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *dy, *du, *dv;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += convert->in_x * 4;

  dy = FRAME_GET_Y_LINE (dest, convert->out_y);
  dy += convert->out_x;
  du = FRAME_GET_U_LINE (dest, convert->out_y);
  du += convert->out_x >> 1;
  dv = FRAME_GET_V_LINE (dest, convert->out_y);
  dv += convert->out_x >> 1;

  /* only works for even width */
  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_Y_STRIDE (dest);
    tasks[i].dustride = FRAME_GET_U_STRIDE (dest);
    tasks[i].dvstride = FRAME_GET_V_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = dy + i * lines_per_thread * tasks[i].dstride;
    tasks[i].du = du + i * lines_per_thread * tasks[i].dustride;
    tasks[i].dv = dv + i * lines_per_thread * tasks[i].dvstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_Y42B_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_AYUV_Y444_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_Y444 (task->d, task->dstride, task->du,
      task->dustride, task->dv, task->dvstride,
      task->s, task->sstride, task->width, task->height);
}

static void
convert_AYUV_Y444 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *s, *dy, *du, *dv;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += convert->in_x * 4;

  dy = FRAME_GET_Y_LINE (dest, convert->out_y);
  dy += convert->out_x;
  du = FRAME_GET_U_LINE (dest, convert->out_y);
  du += convert->out_x;
  dv = FRAME_GET_V_LINE (dest, convert->out_y);
  dv += convert->out_x;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_Y_STRIDE (dest);
    tasks[i].dustride = FRAME_GET_U_STRIDE (dest);
    tasks[i].dvstride = FRAME_GET_V_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = dy + i * lines_per_thread * tasks[i].dstride;
    tasks[i].du = du + i * lines_per_thread * tasks[i].dustride;
    tasks[i].dv = dv + i * lines_per_thread * tasks[i].dvstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_Y444_task, (gpointer) tasks_p);
  convert_fill_border (convert, dest);
}

static void
convert_Y42B_YUY2_task (FConvertPlaneTask * task)
{
  video_orc_convert_Y42B_YUY2 (task->d, task->dstride,
      task->s, task->sstride,
      task->su, task->sustride,
      task->sv, task->svstride, (task->width + 1) / 2, task->height);
}

static void
convert_Y42B_YUY2 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *sy, *su, *sv, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  sy = FRAME_GET_Y_LINE (src, convert->in_y);
  sy += convert->in_x;
  su = FRAME_GET_U_LINE (src, convert->in_y);
  su += convert->in_x >> 1;
  sv = FRAME_GET_V_LINE (src, convert->in_y);
  sv += convert->in_x >> 1;

  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (GST_ROUND_UP_2 (convert->out_x) * 2);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_Y_STRIDE (src);
    tasks[i].sustride = FRAME_GET_U_STRIDE (src);
    tasks[i].svstride = FRAME_GET_V_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = sy + i * lines_per_thread * tasks[i].sstride;
    tasks[i].su = su + i * lines_per_thread * tasks[i].sustride;
    tasks[i].sv = sv + i * lines_per_thread * tasks[i].svstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_Y42B_YUY2_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_Y42B_UYVY_task (FConvertPlaneTask * task)
{
  video_orc_convert_Y42B_UYVY (task->d, task->dstride,
      task->s, task->sstride,
      task->su, task->sustride,
      task->sv, task->svstride, (task->width + 1) / 2, task->height);
}

static void
convert_Y42B_UYVY (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *sy, *su, *sv, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  sy = FRAME_GET_Y_LINE (src, convert->in_y);
  sy += convert->in_x;
  su = FRAME_GET_U_LINE (src, convert->in_y);
  su += convert->in_x >> 1;
  sv = FRAME_GET_V_LINE (src, convert->in_y);
  sv += convert->in_x >> 1;

  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (GST_ROUND_UP_2 (convert->out_x) * 2);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_Y_STRIDE (src);
    tasks[i].sustride = FRAME_GET_U_STRIDE (src);
    tasks[i].svstride = FRAME_GET_V_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = sy + i * lines_per_thread * tasks[i].sstride;
    tasks[i].su = su + i * lines_per_thread * tasks[i].sustride;
    tasks[i].sv = sv + i * lines_per_thread * tasks[i].svstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_Y42B_UYVY_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_Y42B_AYUV_task (FConvertPlaneTask * task)
{
  video_orc_convert_Y42B_AYUV (task->d, task->dstride, task->s,
      task->sstride,
      task->su,
      task->sustride,
      task->sv, task->svstride, task->alpha, task->width / 2, task->height);
}

static void
convert_Y42B_AYUV (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *sy, *su, *sv, *d;
  guint8 alpha = MIN (convert->alpha_value, 255);
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  sy = FRAME_GET_Y_LINE (src, convert->in_y);
  sy += convert->in_x;
  su = FRAME_GET_U_LINE (src, convert->in_y);
  su += convert->in_x >> 1;
  sv = FRAME_GET_V_LINE (src, convert->in_y);
  sv += convert->in_x >> 1;

  d = FRAME_GET_LINE (dest, convert->out_y);
  d += convert->out_x * 4;

  /* only for even width */
  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_Y_STRIDE (src);
    tasks[i].sustride = FRAME_GET_U_STRIDE (src);
    tasks[i].svstride = FRAME_GET_V_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = sy + i * lines_per_thread * tasks[i].sstride;
    tasks[i].su = su + i * lines_per_thread * tasks[i].sustride;
    tasks[i].sv = sv + i * lines_per_thread * tasks[i].svstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].alpha = alpha;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_Y42B_AYUV_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_Y444_YUY2_task (FConvertPlaneTask * task)
{
  video_orc_convert_Y444_YUY2 (task->d, task->dstride, task->s,
      task->sstride,
      task->su,
      task->sustride, task->sv, task->svstride, task->width / 2, task->height);
}

static void
convert_Y444_YUY2 (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *sy, *su, *sv, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  sy = FRAME_GET_Y_LINE (src, convert->in_y);
  sy += convert->in_x;
  su = FRAME_GET_U_LINE (src, convert->in_y);
  su += convert->in_x;
  sv = FRAME_GET_V_LINE (src, convert->in_y);
  sv += convert->in_x;

  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (GST_ROUND_UP_2 (convert->out_x) * 2);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_Y_STRIDE (src);
    tasks[i].sustride = FRAME_GET_U_STRIDE (src);
    tasks[i].svstride = FRAME_GET_V_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = sy + i * lines_per_thread * tasks[i].sstride;
    tasks[i].su = su + i * lines_per_thread * tasks[i].sustride;
    tasks[i].sv = sv + i * lines_per_thread * tasks[i].svstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_Y444_YUY2_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_Y444_UYVY_task (FConvertPlaneTask * task)
{
  video_orc_convert_Y444_UYVY (task->d, task->dstride, task->s,
      task->sstride,
      task->su,
      task->sustride, task->sv, task->svstride, task->width / 2, task->height);
}

static void
convert_Y444_UYVY (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *sy, *su, *sv, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  sy = FRAME_GET_Y_LINE (src, convert->in_y);
  sy += convert->in_x;
  su = FRAME_GET_U_LINE (src, convert->in_y);
  su += convert->in_x;
  sv = FRAME_GET_V_LINE (src, convert->in_y);
  sv += convert->in_x;

  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (GST_ROUND_UP_2 (convert->out_x) * 2);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_Y_STRIDE (src);
    tasks[i].sustride = FRAME_GET_U_STRIDE (src);
    tasks[i].svstride = FRAME_GET_V_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = sy + i * lines_per_thread * tasks[i].sstride;
    tasks[i].su = su + i * lines_per_thread * tasks[i].sustride;
    tasks[i].sv = sv + i * lines_per_thread * tasks[i].svstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_Y444_UYVY_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_Y444_AYUV_task (FConvertPlaneTask * task)
{
  video_orc_convert_Y444_AYUV (task->d, task->dstride, task->s,
      task->sstride,
      task->su,
      task->sustride,
      task->sv, task->svstride, task->alpha, task->width, task->height);
}

static void
convert_Y444_AYUV (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  guint8 *sy, *su, *sv, *d;
  guint8 alpha = MIN (convert->alpha_value, 255);
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  sy = FRAME_GET_Y_LINE (src, convert->in_y);
  sy += convert->in_x;
  su = FRAME_GET_U_LINE (src, convert->in_y);
  su += convert->in_x;
  sv = FRAME_GET_V_LINE (src, convert->in_y);
  sv += convert->in_x;

  d = FRAME_GET_LINE (dest, convert->out_y);
  d += convert->out_x * 4;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_Y_STRIDE (src);
    tasks[i].sustride = FRAME_GET_U_STRIDE (src);
    tasks[i].svstride = FRAME_GET_V_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = sy + i * lines_per_thread * tasks[i].sstride;
    tasks[i].su = su + i * lines_per_thread * tasks[i].sustride;
    tasks[i].sv = sv + i * lines_per_thread * tasks[i].svstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].alpha = alpha;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_Y444_AYUV_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
static void
convert_AYUV_ARGB_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_ARGB (task->d, task->dstride, task->s,
      task->sstride, task->data->im[0][0], task->data->im[0][2],
      task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
      task->width, task->height);
}

static void
convert_AYUV_ARGB (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  MatrixData *data = &convert->convert_matrix;
  guint8 *s, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (convert->in_x * 4);
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (convert->out_x * 4);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].data = data;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_ARGB_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_AYUV_BGRA_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_BGRA (task->d, task->dstride, task->s,
      task->sstride, task->data->im[0][0], task->data->im[0][2],
      task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
      task->width, task->height);
}

static void
convert_AYUV_BGRA (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  MatrixData *data = &convert->convert_matrix;
  guint8 *s, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (convert->in_x * 4);
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (convert->out_x * 4);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].data = data;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_BGRA_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_AYUV_ABGR_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_ABGR (task->d, task->dstride, task->s,
      task->sstride, task->data->im[0][0], task->data->im[0][2],
      task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
      task->width, task->height);
}

static void
convert_AYUV_ABGR (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  MatrixData *data = &convert->convert_matrix;
  guint8 *s, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (convert->in_x * 4);
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (convert->out_x * 4);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].data = data;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_ABGR_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_AYUV_RGBA_task (FConvertPlaneTask * task)
{
  video_orc_convert_AYUV_RGBA (task->d, task->dstride, task->s,
      task->sstride, task->data->im[0][0], task->data->im[0][2],
      task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
      task->width, task->height);
}

static void
convert_AYUV_RGBA (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  gint width = convert->in_width;
  gint height = convert->in_height;
  MatrixData *data = &convert->convert_matrix;
  guint8 *s, *d;
  FConvertPlaneTask *tasks;
  FConvertPlaneTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_LINE (src, convert->in_y);
  s += (convert->in_x * 4);
  d = FRAME_GET_LINE (dest, convert->out_y);
  d += (convert->out_x * 4);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertPlaneTask, n_threads);
  tasks_p = g_newa (FConvertPlaneTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_STRIDE (dest);
    tasks[i].sstride = FRAME_GET_STRIDE (src);
    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = width;
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, height);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].data = data;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_AYUV_RGBA_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}
#endif

static void
convert_I420_BGRA_task (FConvertTask * task)
{
  gint i;

  for (i = task->height_0; i < task->height_1; i++) {
    guint8 *sy, *su, *sv, *d;

    d = FRAME_GET_LINE (task->dest, i + task->out_y);
    d += (task->out_x * 4);
    sy = FRAME_GET_Y_LINE (task->src, i + task->in_y);
    sy += task->in_x;
    su = FRAME_GET_U_LINE (task->src, (i + task->in_y) >> 1);
    su += (task->in_x >> 1);
    sv = FRAME_GET_V_LINE (task->src, (i + task->in_y) >> 1);
    sv += (task->in_x >> 1);

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
    video_orc_convert_I420_BGRA (d, sy, su, sv,
        task->data->im[0][0], task->data->im[0][2],
        task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
        task->width);
#else
    video_orc_convert_I420_ARGB (d, sy, su, sv,
        task->data->im[0][0], task->data->im[0][2],
        task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
        task->width);
#endif
  }
}

static void
convert_I420_BGRA (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  int i;
  gint width = convert->in_width;
  gint height = convert->in_height;
  MatrixData *data = &convert->convert_matrix;
  FConvertTask *tasks;
  FConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertTask, n_threads);
  tasks_p = g_newa (FConvertTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].src = src;
    tasks[i].dest = dest;

    tasks[i].width = width;
    tasks[i].data = data;
    tasks[i].in_x = convert->in_x;
    tasks[i].in_y = convert->in_y;
    tasks[i].out_x = convert->out_x;
    tasks[i].out_y = convert->out_y;

    tasks[i].height_0 = i * lines_per_thread;
    tasks[i].height_1 = tasks[i].height_0 + lines_per_thread;
    tasks[i].height_1 = MIN (height, tasks[i].height_1);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_I420_BGRA_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_I420_ARGB_task (FConvertTask * task)
{
  gint i;

  for (i = task->height_0; i < task->height_1; i++) {
    guint8 *sy, *su, *sv, *d;

    d = FRAME_GET_LINE (task->dest, i + task->out_y);
    d += (task->out_x * 4);
    sy = FRAME_GET_Y_LINE (task->src, i + task->in_y);
    sy += task->in_x;
    su = FRAME_GET_U_LINE (task->src, (i + task->in_y) >> 1);
    su += (task->in_x >> 1);
    sv = FRAME_GET_V_LINE (task->src, (i + task->in_y) >> 1);
    sv += (task->in_x >> 1);

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
    video_orc_convert_I420_ARGB (d, sy, su, sv,
        task->data->im[0][0], task->data->im[0][2],
        task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
        task->width);
#else
    video_orc_convert_I420_BGRA (d, sy, su, sv,
        task->data->im[0][0], task->data->im[0][2],
        task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
        task->width);
#endif
  }
}

static void
convert_I420_ARGB (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  int i;
  gint width = convert->in_width;
  gint height = convert->in_height;
  MatrixData *data = &convert->convert_matrix;
  FConvertTask *tasks;
  FConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertTask, n_threads);
  tasks_p = g_newa (FConvertTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].src = src;
    tasks[i].dest = dest;

    tasks[i].width = width;
    tasks[i].data = data;
    tasks[i].in_x = convert->in_x;
    tasks[i].in_y = convert->in_y;
    tasks[i].out_x = convert->out_x;
    tasks[i].out_y = convert->out_y;

    tasks[i].height_0 = i * lines_per_thread;
    tasks[i].height_1 = tasks[i].height_0 + lines_per_thread;
    tasks[i].height_1 = MIN (height, tasks[i].height_1);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_I420_ARGB_task, (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
convert_I420_pack_ARGB_task (FConvertTask * task)
{
  gint i;
  gpointer d[GST_VIDEO_MAX_PLANES];

  d[0] = FRAME_GET_LINE (task->dest, 0);
  d[0] =
      (guint8 *) d[0] +
      task->out_x * GST_VIDEO_FORMAT_INFO_PSTRIDE (task->dest->info.finfo, 0);

  for (i = task->height_0; i < task->height_1; i++) {
    guint8 *sy, *su, *sv;

    sy = FRAME_GET_Y_LINE (task->src, i + task->in_y);
    sy += task->in_x;
    su = FRAME_GET_U_LINE (task->src, (i + task->in_y) >> 1);
    su += (task->in_x >> 1);
    sv = FRAME_GET_V_LINE (task->src, (i + task->in_y) >> 1);
    sv += (task->in_x >> 1);

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
    video_orc_convert_I420_ARGB (task->tmpline, sy, su, sv,
        task->data->im[0][0], task->data->im[0][2],
        task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
        task->width);
#else
    video_orc_convert_I420_BGRA (task->tmpline, sy, su, sv,
        task->data->im[0][0], task->data->im[0][2],
        task->data->im[2][1], task->data->im[1][1], task->data->im[1][2],
        task->width);
#endif
    task->dest->info.finfo->pack_func (task->dest->info.finfo,
        (GST_VIDEO_FRAME_IS_INTERLACED (task->dest) ?
            GST_VIDEO_PACK_FLAG_INTERLACED :
            GST_VIDEO_PACK_FLAG_NONE),
        task->tmpline, 0, d, task->dest->info.stride,
        task->dest->info.chroma_site, i + task->out_y, task->width);
  }
}

static void
convert_I420_pack_ARGB (GstVideoConverter * convert, const GstVideoFrame * src,
    GstVideoFrame * dest)
{
  int i;
  gint width = convert->in_width;
  gint height = convert->in_height;
  MatrixData *data = &convert->convert_matrix;
  FConvertTask *tasks;
  FConvertTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FConvertTask, n_threads);
  tasks_p = g_newa (FConvertTask *, n_threads);

  lines_per_thread = (height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].src = src;
    tasks[i].dest = dest;

    tasks[i].width = width;
    tasks[i].data = data;
    tasks[i].in_x = convert->in_x;
    tasks[i].in_y = convert->in_y;
    tasks[i].out_x = convert->out_x;
    tasks[i].out_y = convert->out_y;
    tasks[i].tmpline = convert->tmpline[i];

    tasks[i].height_0 = i * lines_per_thread;
    tasks[i].height_1 = tasks[i].height_0 + lines_per_thread;
    tasks[i].height_1 = MIN (height, tasks[i].height_1);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_I420_pack_ARGB_task,
      (gpointer) tasks_p);

  convert_fill_border (convert, dest);
}

static void
memset_u24 (guint8 * data, guint8 col[3], unsigned int n)
{
  unsigned int i;

  for (i = 0; i < n; i++) {
    data[0] = col[0];
    data[1] = col[1];
    data[2] = col[2];
    data += 3;
  }
}

static void
memset_u32_16 (guint8 * data, guint8 col[4], unsigned int n)
{
  unsigned int i;

  for (i = 0; i < n; i += 2) {
    data[0] = col[0];
    data[1] = col[1];
    if (i + 1 < n) {
      data[2] = col[2];
      data[3] = col[3];
    }
    data += 4;
  }
}

#define MAKE_BORDER_FUNC(func)                                                  \
        for (i = 0; i < out_y; i++)                                             \
          func (FRAME_GET_PLANE_LINE (dest, k, i), col, out_maxwidth);          \
        if (rb_width || lb_width) {                                             \
          for (i = 0; i < out_height; i++) {                                    \
            guint8 *d = FRAME_GET_PLANE_LINE (dest, k, i + out_y);              \
            if (lb_width)                                                       \
              func (d, col, lb_width);                                          \
            if (rb_width)                                                       \
              func (d + (pstride * r_border), col, rb_width);                   \
          }                                                                     \
        }                                                                       \
        for (i = out_y + out_height; i < out_maxheight; i++)                    \
          func (FRAME_GET_PLANE_LINE (dest, k, i), col, out_maxwidth);          \

static void
convert_fill_border (GstVideoConverter * convert, GstVideoFrame * dest)
{
  int k, n_planes;
  const GstVideoFormatInfo *out_finfo;

  if (!convert->fill_border || !convert->borderline)
    return;

  out_finfo = convert->out_info.finfo;

  n_planes = GST_VIDEO_FRAME_N_PLANES (dest);

  for (k = 0; k < n_planes; k++) {
    gint i, out_x, out_y, out_width, out_height, pstride, pgroup;
    gint r_border, lb_width, rb_width;
    gint out_maxwidth, out_maxheight;
    gpointer borders;

    out_x = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (out_finfo, k, convert->out_x);
    out_y = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (out_finfo, k, convert->out_y);
    out_width =
        GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (out_finfo, k, convert->out_width);
    out_height =
        GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (out_finfo, k, convert->out_height);
    out_maxwidth =
        GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (out_finfo, k, convert->out_maxwidth);
    out_maxheight =
        GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (out_finfo, k,
        convert->out_maxheight);

    pstride = GST_VIDEO_FORMAT_INFO_PSTRIDE (out_finfo, k);

    switch (GST_VIDEO_FORMAT_INFO_FORMAT (out_finfo)) {
      case GST_VIDEO_FORMAT_YUY2:
      case GST_VIDEO_FORMAT_YVYU:
      case GST_VIDEO_FORMAT_UYVY:
        pgroup = 42;
        out_maxwidth = GST_ROUND_UP_2 (out_maxwidth);
        break;
      default:
        pgroup = pstride;
        break;
    }

    r_border = out_x + out_width;
    rb_width = out_maxwidth - r_border;
    lb_width = out_x;

    borders = &convert->borders[k];

    switch (pgroup) {
      case 1:
      {
        guint8 col = ((guint8 *) borders)[0];
        MAKE_BORDER_FUNC (memset);
        break;
      }
      case 2:
      {
        guint16 col = ((guint16 *) borders)[0];
        MAKE_BORDER_FUNC (video_orc_splat_u16);
        break;
      }
      case 3:
      {
        guint8 col[3];
        col[0] = ((guint8 *) borders)[0];
        col[1] = ((guint8 *) borders)[1];
        col[2] = ((guint8 *) borders)[2];
        MAKE_BORDER_FUNC (memset_u24);
        break;
      }
      case 4:
      {
        guint32 col = ((guint32 *) borders)[0];
        MAKE_BORDER_FUNC (video_orc_splat_u32);
        break;
      }
      case 8:
      {
        guint64 col = ((guint64 *) borders)[0];
        MAKE_BORDER_FUNC (video_orc_splat_u64);
        break;
      }
      case 42:
      {
        guint8 col[4];
        col[0] = ((guint8 *) borders)[0];
        col[2] = ((guint8 *) borders)[2];
        col[1] = ((guint8 *) borders)[r_border & 1 ? 3 : 1];
        col[3] = ((guint8 *) borders)[r_border & 1 ? 1 : 3];
        MAKE_BORDER_FUNC (memset_u32_16);
        break;
      }
      default:
        break;
    }
  }
}

typedef struct
{
  const guint8 *s, *s2;
  guint8 *d, *d2;
  gint sstride, dstride;
  gint width, height;
  gint fill;
} FSimpleScaleTask;

static void
convert_plane_fill_task (FSimpleScaleTask * task)
{
  video_orc_memset_2d (task->d, task->dstride,
      task->fill, task->width, task->height);
}

static void
convert_plane_fill (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane)
{
  guint8 *d;
  FSimpleScaleTask *tasks;
  FSimpleScaleTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  d = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane]);
  d += convert->fout_x[plane];

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FSimpleScaleTask, n_threads);
  tasks_p = g_newa (FSimpleScaleTask *, n_threads);
  lines_per_thread = (convert->fout_height[plane] + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].d = d + i * lines_per_thread * convert->fout_width[plane];

    tasks[i].fill = convert->ffill[plane];
    tasks[i].width = convert->fout_width[plane];
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, convert->fout_height[plane]);
    tasks[i].height -= i * lines_per_thread;
    tasks[i].dstride = FRAME_GET_PLANE_STRIDE (dest, plane);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_plane_fill_task, (gpointer) tasks_p);
}

static void
convert_plane_h_double_task (FSimpleScaleTask * task)
{
  video_orc_planar_chroma_422_444 (task->d,
      task->dstride, task->s, task->sstride, task->width / 2, task->height);
}

static void
convert_plane_h_double (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane)
{
  guint8 *s, *d;
  gint splane = convert->fsplane[plane];
  FSimpleScaleTask *tasks;
  FSimpleScaleTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_PLANE_LINE (src, splane, convert->fin_y[splane]);
  s += convert->fin_x[splane];
  d = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane]);
  d += convert->fout_x[plane];

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FSimpleScaleTask, n_threads);
  tasks_p = g_newa (FSimpleScaleTask *, n_threads);
  lines_per_thread = (convert->fout_height[plane] + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_PLANE_STRIDE (dest, plane);
    tasks[i].sstride = FRAME_GET_PLANE_STRIDE (src, splane);

    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = convert->fout_width[plane];
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, convert->fout_height[plane]);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_plane_h_double_task,
      (gpointer) tasks_p);
}

static void
convert_plane_h_halve_task (FSimpleScaleTask * task)
{
  video_orc_planar_chroma_444_422 (task->d,
      task->dstride, task->s, task->sstride, task->width, task->height);
}

static void
convert_plane_h_halve (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane)
{
  guint8 *s, *d;
  gint splane = convert->fsplane[plane];
  FSimpleScaleTask *tasks;
  FSimpleScaleTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_PLANE_LINE (src, splane, convert->fin_y[splane]);
  s += convert->fin_x[splane];
  d = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane]);
  d += convert->fout_x[plane];

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FSimpleScaleTask, n_threads);
  tasks_p = g_newa (FSimpleScaleTask *, n_threads);
  lines_per_thread = (convert->fout_height[plane] + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].dstride = FRAME_GET_PLANE_STRIDE (dest, plane);
    tasks[i].sstride = FRAME_GET_PLANE_STRIDE (src, splane);

    tasks[i].d = d + i * lines_per_thread * tasks[i].dstride;
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride;

    tasks[i].width = convert->fout_width[plane];
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, convert->fout_height[plane]);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_plane_h_halve_task, (gpointer) tasks_p);
}

static void
convert_plane_v_double_task (FSimpleScaleTask * task)
{
  video_orc_planar_chroma_420_422 (task->d, 2 * task->dstride, task->d2,
      2 * task->dstride, task->s, task->sstride, task->width, task->height / 2);
}

static void
convert_plane_v_double (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane)
{
  guint8 *s, *d1, *d2;
  gint ds, splane = convert->fsplane[plane];
  FSimpleScaleTask *tasks;
  FSimpleScaleTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_PLANE_LINE (src, splane, convert->fin_y[splane]);
  s += convert->fin_x[splane];
  d1 = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane]);
  d1 += convert->fout_x[plane];
  d2 = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane] + 1);
  d2 += convert->fout_x[plane];
  ds = FRAME_GET_PLANE_STRIDE (dest, plane);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FSimpleScaleTask, n_threads);
  tasks_p = g_newa (FSimpleScaleTask *, n_threads);
  lines_per_thread =
      GST_ROUND_UP_2 ((convert->fout_height[plane] + n_threads -
          1) / n_threads);

  for (i = 0; i < n_threads; i++) {
    tasks[i].d = d1 + i * lines_per_thread * ds;
    tasks[i].d2 = d2 + i * lines_per_thread * ds;
    tasks[i].dstride = ds;
    tasks[i].sstride = FRAME_GET_PLANE_STRIDE (src, splane);
    tasks[i].s = s + i * lines_per_thread * tasks[i].sstride / 2;

    tasks[i].width = convert->fout_width[plane];
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, convert->fout_height[plane]);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_plane_v_double_task,
      (gpointer) tasks_p);
}

static void
convert_plane_v_halve_task (FSimpleScaleTask * task)
{
  video_orc_planar_chroma_422_420 (task->d, task->dstride, task->s,
      2 * task->sstride, task->s2, 2 * task->sstride, task->width,
      task->height);
}

static void
convert_plane_v_halve (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane)
{
  guint8 *s1, *s2, *d;
  gint ss, ds, splane = convert->fsplane[plane];
  FSimpleScaleTask *tasks;
  FSimpleScaleTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s1 = FRAME_GET_PLANE_LINE (src, splane, convert->fin_y[splane]);
  s1 += convert->fin_x[splane];
  s2 = FRAME_GET_PLANE_LINE (src, splane, convert->fin_y[splane] + 1);
  s2 += convert->fin_x[splane];
  d = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane]);
  d += convert->fout_x[plane];

  ss = FRAME_GET_PLANE_STRIDE (src, splane);
  ds = FRAME_GET_PLANE_STRIDE (dest, plane);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FSimpleScaleTask, n_threads);
  tasks_p = g_newa (FSimpleScaleTask *, n_threads);
  lines_per_thread = (convert->fout_height[plane] + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].d = d + i * lines_per_thread * ds;
    tasks[i].dstride = ds;
    tasks[i].s = s1 + i * lines_per_thread * ss * 2;
    tasks[i].s2 = s2 + i * lines_per_thread * ss * 2;
    tasks[i].sstride = ss;

    tasks[i].width = convert->fout_width[plane];
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, convert->fout_height[plane]);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_plane_v_halve_task, (gpointer) tasks_p);
}

static void
convert_plane_hv_double_task (FSimpleScaleTask * task)
{
  video_orc_planar_chroma_420_444 (task->d, 2 * task->dstride, task->d2,
      2 * task->dstride, task->s, task->sstride, (task->width + 1) / 2,
      task->height / 2);
}

static void
convert_plane_hv_double (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane)
{
  guint8 *s, *d1, *d2;
  gint ss, ds, splane = convert->fsplane[plane];
  FSimpleScaleTask *tasks;
  FSimpleScaleTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s = FRAME_GET_PLANE_LINE (src, splane, convert->fin_y[splane]);
  s += convert->fin_x[splane];
  d1 = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane]);
  d1 += convert->fout_x[plane];
  d2 = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane] + 1);
  d2 += convert->fout_x[plane];
  ss = FRAME_GET_PLANE_STRIDE (src, splane);
  ds = FRAME_GET_PLANE_STRIDE (dest, plane);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FSimpleScaleTask, n_threads);
  tasks_p = g_newa (FSimpleScaleTask *, n_threads);
  lines_per_thread =
      GST_ROUND_UP_2 ((convert->fout_height[plane] + n_threads -
          1) / n_threads);

  for (i = 0; i < n_threads; i++) {
    tasks[i].d = d1 + i * lines_per_thread * ds;
    tasks[i].d2 = d2 + i * lines_per_thread * ds;
    tasks[i].dstride = ds;
    tasks[i].sstride = ss;
    tasks[i].s = s + i * lines_per_thread * ss / 2;

    tasks[i].width = convert->fout_width[plane];
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, convert->fout_height[plane]);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_plane_hv_double_task,
      (gpointer) tasks_p);
}

static void
convert_plane_hv_halve_task (FSimpleScaleTask * task)
{
  video_orc_planar_chroma_444_420 (task->d, task->dstride, task->s,
      2 * task->sstride, task->s2, 2 * task->sstride, task->width,
      task->height);
}

static void
convert_plane_hv_halve (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane)
{
  guint8 *s1, *s2, *d;
  gint ss, ds, splane = convert->fsplane[plane];
  FSimpleScaleTask *tasks;
  FSimpleScaleTask **tasks_p;
  gint n_threads;
  gint lines_per_thread;
  gint i;

  s1 = FRAME_GET_PLANE_LINE (src, splane, convert->fin_y[splane]);
  s1 += convert->fin_x[splane];
  s2 = FRAME_GET_PLANE_LINE (src, splane, convert->fin_y[splane] + 1);
  s2 += convert->fin_x[splane];
  d = FRAME_GET_PLANE_LINE (dest, plane, convert->fout_y[plane]);
  d += convert->fout_x[plane];
  ss = FRAME_GET_PLANE_STRIDE (src, splane);
  ds = FRAME_GET_PLANE_STRIDE (dest, plane);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FSimpleScaleTask, n_threads);
  tasks_p = g_newa (FSimpleScaleTask *, n_threads);
  lines_per_thread = (convert->fout_height[plane] + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].d = d + i * lines_per_thread * ds;
    tasks[i].dstride = ds;
    tasks[i].s = s1 + i * lines_per_thread * ss * 2;
    tasks[i].s2 = s2 + i * lines_per_thread * ss * 2;
    tasks[i].sstride = ss;

    tasks[i].width = convert->fout_width[plane];
    tasks[i].height = (i + 1) * lines_per_thread;
    tasks[i].height = MIN (tasks[i].height, convert->fout_height[plane]);
    tasks[i].height -= i * lines_per_thread;

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_plane_hv_halve_task,
      (gpointer) tasks_p);
}

typedef struct
{
  GstVideoScaler *h_scaler, *v_scaler;
  GstVideoFormat format;
  const guint8 *s;
  guint8 *d;
  gint sstride, dstride;
  guint x, y, w, h;
} FScaleTask;

static void
convert_plane_hv_task (FScaleTask * task)
{
  gst_video_scaler_2d (task->h_scaler, task->v_scaler, task->format,
      (guint8 *) task->s, task->sstride,
      task->d, task->dstride, task->x, task->y, task->w, task->h);
}

static void
convert_plane_hv (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest, gint plane)
{
  gint in_x, in_y, out_x, out_y, out_width, out_height;
  GstVideoFormat format;
  gint splane = convert->fsplane[plane];
  guint8 *s, *d;
  gint sstride, dstride;
  FScaleTask *tasks;
  FScaleTask **tasks_p;
  gint i, n_threads, lines_per_thread;

  in_x = convert->fin_x[splane];
  in_y = convert->fin_y[splane];
  out_x = convert->fout_x[plane];
  out_y = convert->fout_y[plane];
  out_width = convert->fout_width[plane];
  out_height = convert->fout_height[plane];
  format = convert->fformat[plane];

  s = FRAME_GET_PLANE_LINE (src, splane, in_y);
  s += in_x;
  d = FRAME_GET_PLANE_LINE (dest, plane, out_y);
  d += out_x;

  sstride = FRAME_GET_PLANE_STRIDE (src, splane);
  dstride = FRAME_GET_PLANE_STRIDE (dest, plane);

  n_threads = convert->conversion_runner->n_threads;
  tasks = g_newa (FScaleTask, n_threads);
  tasks_p = g_newa (FScaleTask *, n_threads);

  lines_per_thread = (out_height + n_threads - 1) / n_threads;

  for (i = 0; i < n_threads; i++) {
    tasks[i].h_scaler =
        convert->fh_scaler[plane].scaler ? convert->
        fh_scaler[plane].scaler[i] : NULL;
    tasks[i].v_scaler =
        convert->fv_scaler[plane].scaler ? convert->
        fv_scaler[plane].scaler[i] : NULL;
    tasks[i].format = format;
    tasks[i].s = s;
    tasks[i].d = d;
    tasks[i].sstride = sstride;
    tasks[i].dstride = dstride;

    tasks[i].x = 0;
    tasks[i].w = out_width;

    tasks[i].y = i * lines_per_thread;
    tasks[i].h = tasks[i].y + lines_per_thread;
    tasks[i].h = MIN (out_height, tasks[i].h);

    tasks_p[i] = &tasks[i];
  }

  gst_parallelized_task_runner_run (convert->conversion_runner,
      (GstParallelizedTaskFunc) convert_plane_hv_task, (gpointer) tasks_p);
}

static void
convert_scale_planes (GstVideoConverter * convert,
    const GstVideoFrame * src, GstVideoFrame * dest)
{
  int i, n_planes;

  n_planes = GST_VIDEO_FRAME_N_PLANES (dest);
  for (i = 0; i < n_planes; i++) {
    if (convert->fconvert[i])
      convert->fconvert[i] (convert, src, dest, i);
  }
  convert_fill_border (convert, dest);
}

static GstVideoFormat
get_scale_format (GstVideoFormat format, gint plane)
{
  GstVideoFormat res = GST_VIDEO_FORMAT_UNKNOWN;

  switch (format) {
    case GST_VIDEO_FORMAT_I420:
    case GST_VIDEO_FORMAT_YV12:
    case GST_VIDEO_FORMAT_Y41B:
    case GST_VIDEO_FORMAT_Y42B:
    case GST_VIDEO_FORMAT_Y444:
    case GST_VIDEO_FORMAT_GRAY8:
    case GST_VIDEO_FORMAT_A420:
    case GST_VIDEO_FORMAT_YUV9:
    case GST_VIDEO_FORMAT_YVU9:
    case GST_VIDEO_FORMAT_GBR:
    case GST_VIDEO_FORMAT_GBRA:
      res = GST_VIDEO_FORMAT_GRAY8;
      break;
    case GST_VIDEO_FORMAT_GRAY16_BE:
    case GST_VIDEO_FORMAT_GRAY16_LE:
      res = GST_VIDEO_FORMAT_GRAY16_BE;
      break;
    case GST_VIDEO_FORMAT_YUY2:
    case GST_VIDEO_FORMAT_UYVY:
    case GST_VIDEO_FORMAT_VYUY:
    case GST_VIDEO_FORMAT_YVYU:
    case GST_VIDEO_FORMAT_AYUV:
    case GST_VIDEO_FORMAT_VUYA:
    case GST_VIDEO_FORMAT_RGBx:
    case GST_VIDEO_FORMAT_BGRx:
    case GST_VIDEO_FORMAT_xRGB:
    case GST_VIDEO_FORMAT_xBGR:
    case GST_VIDEO_FORMAT_RGBA:
    case GST_VIDEO_FORMAT_BGRA:
    case GST_VIDEO_FORMAT_ARGB:
    case GST_VIDEO_FORMAT_ABGR:
    case GST_VIDEO_FORMAT_RGB:
    case GST_VIDEO_FORMAT_BGR:
    case GST_VIDEO_FORMAT_v308:
    case GST_VIDEO_FORMAT_IYU2:
    case GST_VIDEO_FORMAT_ARGB64:
    case GST_VIDEO_FORMAT_AYUV64:
      res = format;
      break;
    case GST_VIDEO_FORMAT_RGB15:
    case GST_VIDEO_FORMAT_BGR15:
    case GST_VIDEO_FORMAT_RGB16:
    case GST_VIDEO_FORMAT_BGR16:
      res = GST_VIDEO_FORMAT_NV12;
      break;
    case GST_VIDEO_FORMAT_NV12:
    case GST_VIDEO_FORMAT_NV21:
    case GST_VIDEO_FORMAT_NV16:
    case GST_VIDEO_FORMAT_NV61:
    case GST_VIDEO_FORMAT_NV24:
      res = plane == 0 ? GST_VIDEO_FORMAT_GRAY8 : GST_VIDEO_FORMAT_NV12;
      break;
    case GST_VIDEO_FORMAT_UNKNOWN:
    case GST_VIDEO_FORMAT_ENCODED:
    case GST_VIDEO_FORMAT_v210:
    case GST_VIDEO_FORMAT_v216:
    case GST_VIDEO_FORMAT_Y210:
    case GST_VIDEO_FORMAT_Y410:
    case GST_VIDEO_FORMAT_UYVP:
    case GST_VIDEO_FORMAT_RGB8P:
    case GST_VIDEO_FORMAT_IYU1:
    case GST_VIDEO_FORMAT_r210:
    case GST_VIDEO_FORMAT_I420_10BE:
    case GST_VIDEO_FORMAT_I420_10LE:
    case GST_VIDEO_FORMAT_I422_10BE:
    case GST_VIDEO_FORMAT_I422_10LE:
    case GST_VIDEO_FORMAT_Y444_10BE:
    case GST_VIDEO_FORMAT_Y444_10LE:
    case GST_VIDEO_FORMAT_I420_12BE:
    case GST_VIDEO_FORMAT_I420_12LE:
    case GST_VIDEO_FORMAT_I422_12BE:
    case GST_VIDEO_FORMAT_I422_12LE:
    case GST_VIDEO_FORMAT_Y444_12BE:
    case GST_VIDEO_FORMAT_Y444_12LE:
    case GST_VIDEO_FORMAT_GBR_10BE:
    case GST_VIDEO_FORMAT_GBR_10LE:
    case GST_VIDEO_FORMAT_GBRA_10BE:
    case GST_VIDEO_FORMAT_GBRA_10LE:
    case GST_VIDEO_FORMAT_GBR_12BE:
    case GST_VIDEO_FORMAT_GBR_12LE:
    case GST_VIDEO_FORMAT_GBRA_12BE:
    case GST_VIDEO_FORMAT_GBRA_12LE:
    case GST_VIDEO_FORMAT_NV12_64Z32:
    case GST_VIDEO_FORMAT_A420_10BE:
    case GST_VIDEO_FORMAT_A420_10LE:
    case GST_VIDEO_FORMAT_A422_10BE:
    case GST_VIDEO_FORMAT_A422_10LE:
    case GST_VIDEO_FORMAT_A444_10BE:
    case GST_VIDEO_FORMAT_A444_10LE:
    case GST_VIDEO_FORMAT_P010_10BE:
    case GST_VIDEO_FORMAT_P010_10LE:
    case GST_VIDEO_FORMAT_GRAY10_LE32:
    case GST_VIDEO_FORMAT_NV12_10LE32:
    case GST_VIDEO_FORMAT_NV16_10LE32:
    case GST_VIDEO_FORMAT_NV12_10LE40:
    case GST_VIDEO_FORMAT_BGR10A2_LE:
      res = format;
      g_assert_not_reached ();
      break;
  }
  return res;
}

static gboolean
is_merge_yuv (GstVideoInfo * info)
{
  switch (GST_VIDEO_INFO_FORMAT (info)) {
    case GST_VIDEO_FORMAT_YUY2:
    case GST_VIDEO_FORMAT_YVYU:
    case GST_VIDEO_FORMAT_UYVY:
    case GST_VIDEO_FORMAT_VYUY:
      return TRUE;
    default:
      return FALSE;
  }
}

static gboolean
setup_scale (GstVideoConverter * convert)
{
  int i, n_planes;
  gint method, cr_method, in_width, in_height, out_width, out_height;
  guint taps;
  GstVideoInfo *in_info, *out_info;
  const GstVideoFormatInfo *in_finfo, *out_finfo;
  GstVideoFormat in_format, out_format;
  guint n_threads = convert->conversion_runner->n_threads;

  in_info = &convert->in_info;
  out_info = &convert->out_info;

  in_finfo = in_info->finfo;
  out_finfo = out_info->finfo;

  n_planes = GST_VIDEO_INFO_N_PLANES (out_info);

  method = GET_OPT_RESAMPLER_METHOD (convert);
  if (method == GST_VIDEO_RESAMPLER_METHOD_NEAREST)
    cr_method = method;
  else
    cr_method = GET_OPT_CHROMA_RESAMPLER_METHOD (convert);
  taps = GET_OPT_RESAMPLER_TAPS (convert);

  in_format = GST_VIDEO_INFO_FORMAT (in_info);
  out_format = GST_VIDEO_INFO_FORMAT (out_info);

  switch (in_format) {
    case GST_VIDEO_FORMAT_RGB15:
    case GST_VIDEO_FORMAT_RGB16:
    case GST_VIDEO_FORMAT_BGR15:
    case GST_VIDEO_FORMAT_BGR16:
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
    case GST_VIDEO_FORMAT_GRAY16_BE:
#else
    case GST_VIDEO_FORMAT_GRAY16_LE:
#endif
      if (method != GST_VIDEO_RESAMPLER_METHOD_NEAREST) {
        GST_DEBUG ("%s only with nearest resampling",
            gst_video_format_to_string (in_format));
        return FALSE;
      }
      break;
    default:
      break;
  }

  in_width = convert->in_width;
  in_height = convert->in_height;
  out_width = convert->out_width;
  out_height = convert->out_height;

  if (n_planes == 1 && !GST_VIDEO_FORMAT_INFO_IS_GRAY (out_finfo)) {
    gint pstride;
    guint j;

    if (is_merge_yuv (in_info)) {
      GstVideoScaler *y_scaler, *uv_scaler;

      if (in_width != out_width) {
        convert->fh_scaler[0].scaler = g_new (GstVideoScaler *, n_threads);
        for (j = 0; j < n_threads; j++) {
          y_scaler =
              gst_video_scaler_new (method, GST_VIDEO_SCALER_FLAG_NONE, taps,
              GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (in_finfo, GST_VIDEO_COMP_Y,
                  in_width), GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (out_finfo,
                  GST_VIDEO_COMP_Y, out_width), convert->config);
          uv_scaler =
              gst_video_scaler_new (method, GST_VIDEO_SCALER_FLAG_NONE,
              gst_video_scaler_get_max_taps (y_scaler),
              GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (in_finfo, GST_VIDEO_COMP_U,
                  in_width), GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (out_finfo,
                  GST_VIDEO_COMP_U, out_width), convert->config);

          convert->fh_scaler[0].scaler[j] =
              gst_video_scaler_combine_packed_YUV (y_scaler, uv_scaler,
              in_format, out_format);

          gst_video_scaler_free (y_scaler);
          gst_video_scaler_free (uv_scaler);
        }
      } else {
        convert->fh_scaler[0].scaler = NULL;
      }

      pstride = GST_VIDEO_FORMAT_INFO_PSTRIDE (out_finfo, GST_VIDEO_COMP_Y);
      convert->fin_x[0] = GST_ROUND_UP_2 (convert->in_x) * pstride;
      convert->fout_x[0] = GST_ROUND_UP_2 (convert->out_x) * pstride;

    } else {
      if (in_width != out_width && in_width != 0 && out_width != 0) {
        convert->fh_scaler[0].scaler = g_new (GstVideoScaler *, n_threads);
        for (j = 0; j < n_threads; j++) {
          convert->fh_scaler[0].scaler[j] =
              gst_video_scaler_new (method, GST_VIDEO_SCALER_FLAG_NONE, taps,
              in_width, out_width, convert->config);
        }
      } else {
        convert->fh_scaler[0].scaler = NULL;
      }

      pstride = GST_VIDEO_FORMAT_INFO_PSTRIDE (out_finfo, GST_VIDEO_COMP_R);
      convert->fin_x[0] = convert->in_x * pstride;
      convert->fout_x[0] = convert->out_x * pstride;
    }

    if (in_height != out_height && in_height != 0 && out_height != 0) {
      convert->fv_scaler[0].scaler = g_new (GstVideoScaler *, n_threads);

      for (j = 0; j < n_threads; j++) {
        convert->fv_scaler[0].scaler[j] =
            gst_video_scaler_new (method, GST_VIDEO_SCALER_FLAG_NONE, taps,
            in_height, out_height, convert->config);
      }
    } else {
      convert->fv_scaler[0].scaler = NULL;
    }

    convert->fin_y[0] = convert->in_y;
    convert->fout_y[0] = convert->out_y;
    convert->fout_width[0] = out_width;
    convert->fout_height[0] = out_height;
    convert->fconvert[0] = convert_plane_hv;
    convert->fformat[0] = get_scale_format (in_format, 0);
    convert->fsplane[0] = 0;
  } else {
    for (i = 0; i < n_planes; i++) {
      gint comp, n_comp, j, iw, ih, ow, oh, pstride;
      gboolean need_v_scaler, need_h_scaler;
      GstStructure *config;
      gint resample_method;

      n_comp = GST_VIDEO_FORMAT_INFO_N_COMPONENTS (in_finfo);

      /* find the component in this plane and map it to the plane of
       * the source */
      comp = -1;
      for (j = 0; j < n_comp; j++) {
        if (GST_VIDEO_FORMAT_INFO_PLANE (out_finfo, j) == i) {
          comp = j;
          break;
        }
      }

      iw = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (in_finfo, i, in_width);
      ih = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (in_finfo, i, in_height);
      ow = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (out_finfo, i, out_width);
      oh = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (out_finfo, i, out_height);

      GST_DEBUG ("plane %d: %dx%d -> %dx%d", i, iw, ih, ow, oh);

      convert->fout_width[i] = ow;
      convert->fout_height[i] = oh;

      pstride = GST_VIDEO_FORMAT_INFO_PSTRIDE (out_finfo, i);
      convert->fin_x[i] =
          GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (in_finfo, i, convert->in_x);
      convert->fin_x[i] *= pstride;
      convert->fin_y[i] =
          GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (in_finfo, i, convert->in_y);
      convert->fout_x[i] =
          GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (out_finfo, i, convert->out_x);
      convert->fout_x[i] *= pstride;
      convert->fout_y[i] =
          GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (out_finfo, i, convert->out_y);

      GST_DEBUG ("plane %d: pstride %d", i, pstride);
      GST_DEBUG ("plane %d: in_x %d, in_y %d", i, convert->fin_x[i],
          convert->fin_y[i]);
      GST_DEBUG ("plane %d: out_x %d, out_y %d", i, convert->fout_x[i],
          convert->fout_y[i]);

      if (comp == -1) {
        convert->fconvert[i] = convert_plane_fill;
        if (GST_VIDEO_INFO_IS_YUV (out_info)) {
          if (i == 3)
            convert->ffill[i] = convert->alpha_value;
          if (i == 0)
            convert->ffill[i] = 0x00;
          else
            convert->ffill[i] = 0x80;
        } else {
          if (i == 3)
            convert->ffill[i] = convert->alpha_value;
          else
            convert->ffill[i] = 0x00;
        }
        GST_DEBUG ("plane %d fill %02x", i, convert->ffill[i]);
        continue;
      } else {
        convert->fsplane[i] = GST_VIDEO_FORMAT_INFO_PLANE (in_finfo, comp);
        GST_DEBUG ("plane %d -> %d (comp %d)", i, convert->fsplane[i], comp);
      }

      config = gst_structure_copy (convert->config);

      resample_method = (i == 0 ? method : cr_method);

      need_v_scaler = FALSE;
      need_h_scaler = FALSE;
      if (iw == ow) {
        if (ih == oh) {
          convert->fconvert[i] = convert_plane_hv;
          GST_DEBUG ("plane %d: copy", i);
        } else if (ih == 2 * oh && pstride == 1
            && resample_method == GST_VIDEO_RESAMPLER_METHOD_LINEAR) {
          convert->fconvert[i] = convert_plane_v_halve;
          GST_DEBUG ("plane %d: vertical halve", i);
        } else if (2 * ih == oh && pstride == 1
            && resample_method == GST_VIDEO_RESAMPLER_METHOD_NEAREST) {
          convert->fconvert[i] = convert_plane_v_double;
          GST_DEBUG ("plane %d: vertical double", i);
        } else {
          convert->fconvert[i] = convert_plane_hv;
          GST_DEBUG ("plane %d: vertical scale", i);
          need_v_scaler = TRUE;
        }
      } else if (ih == oh) {
        if (iw == 2 * ow && pstride == 1
            && resample_method == GST_VIDEO_RESAMPLER_METHOD_LINEAR) {
          convert->fconvert[i] = convert_plane_h_halve;
          GST_DEBUG ("plane %d: horizontal halve", i);
        } else if (2 * iw == ow && pstride == 1
            && resample_method == GST_VIDEO_RESAMPLER_METHOD_NEAREST) {
          convert->fconvert[i] = convert_plane_h_double;
          GST_DEBUG ("plane %d: horizontal double", i);
        } else {
          convert->fconvert[i] = convert_plane_hv;
          GST_DEBUG ("plane %d: horizontal scale", i);
          need_h_scaler = TRUE;
        }
      } else {
        if (iw == 2 * ow && ih == 2 * oh && pstride == 1
            && resample_method == GST_VIDEO_RESAMPLER_METHOD_LINEAR) {
          convert->fconvert[i] = convert_plane_hv_halve;
          GST_DEBUG ("plane %d: horizontal/vertical halve", i);
        } else if (2 * iw == ow && 2 * ih == oh && pstride == 1
            && resample_method == GST_VIDEO_RESAMPLER_METHOD_NEAREST) {
          convert->fconvert[i] = convert_plane_hv_double;
          GST_DEBUG ("plane %d: horizontal/vertical double", i);
        } else {
          convert->fconvert[i] = convert_plane_hv;
          GST_DEBUG ("plane %d: horizontal/vertical scale", i);
          need_v_scaler = TRUE;
          need_h_scaler = TRUE;
        }
      }

      if (need_h_scaler && iw != 0 && ow != 0) {
        convert->fh_scaler[i].scaler = g_new (GstVideoScaler *, n_threads);

        for (j = 0; j < n_threads; j++) {
          convert->fh_scaler[i].scaler[j] =
              gst_video_scaler_new (resample_method, GST_VIDEO_SCALER_FLAG_NONE,
              taps, iw, ow, config);
        }
      } else {
        convert->fh_scaler[i].scaler = NULL;
      }

      if (need_v_scaler && ih != 0 && oh != 0) {
        convert->fv_scaler[i].scaler = g_new (GstVideoScaler *, n_threads);

        for (j = 0; j < n_threads; j++) {
          convert->fv_scaler[i].scaler[j] =
              gst_video_scaler_new (resample_method, GST_VIDEO_SCALER_FLAG_NONE,
              taps, ih, oh, config);
        }
      } else {
        convert->fv_scaler[i].scaler = NULL;
      }

      gst_structure_free (config);
      convert->fformat[i] = get_scale_format (in_format, i);
    }
  }

  return TRUE;
}

/* Fast paths */

typedef struct
{
  GstVideoFormat in_format;
  GstVideoFormat out_format;
  gboolean keeps_interlaced;
  gboolean needs_color_matrix;
  gboolean keeps_size;
  gboolean do_crop;
  gboolean do_border;
  gboolean alpha_copy;
  gboolean alpha_set;
  gboolean alpha_mult;
  gint width_align, height_align;
  void (*convert) (GstVideoConverter * convert, const GstVideoFrame * src,
      GstVideoFrame * dest);
} VideoTransform;

static const VideoTransform transforms[] = {
  /* planar -> packed */
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_YUY2, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, FALSE, FALSE, 0, 0, convert_I420_YUY2},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_UYVY, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, FALSE, FALSE, 0, 0, convert_I420_UYVY},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_AYUV, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, TRUE, FALSE, 0, 0, convert_I420_AYUV},

  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_YUY2, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, FALSE, FALSE, 0, 0, convert_I420_YUY2},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_UYVY, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, FALSE, FALSE, 0, 0, convert_I420_UYVY},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_AYUV, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, TRUE, FALSE, 0, 0, convert_I420_AYUV},

  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_YUY2, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_Y42B_YUY2},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_UYVY, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_Y42B_UYVY},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_AYUV, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 1, 0, convert_Y42B_AYUV},

  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_YUY2, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 1, 0, convert_Y444_YUY2},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_UYVY, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 1, 0, convert_Y444_UYVY},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_AYUV, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_Y444_AYUV},

  /* packed -> packed */
  {GST_VIDEO_FORMAT_YUY2, GST_VIDEO_FORMAT_YUY2, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUY2, GST_VIDEO_FORMAT_UYVY, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_UYVY_YUY2},      /* alias */
  {GST_VIDEO_FORMAT_YUY2, GST_VIDEO_FORMAT_AYUV, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 1, 0, convert_YUY2_AYUV},

  {GST_VIDEO_FORMAT_UYVY, GST_VIDEO_FORMAT_UYVY, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_UYVY, GST_VIDEO_FORMAT_YUY2, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_UYVY_YUY2},
  {GST_VIDEO_FORMAT_UYVY, GST_VIDEO_FORMAT_AYUV, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_UYVY_AYUV},

  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_AYUV, TRUE, FALSE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_YUY2, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 1, 0, convert_AYUV_YUY2},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_UYVY, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 1, 0, convert_AYUV_UYVY},

  /* packed -> planar */
  {GST_VIDEO_FORMAT_YUY2, GST_VIDEO_FORMAT_I420, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, FALSE, FALSE, 0, 0, convert_YUY2_I420},
  {GST_VIDEO_FORMAT_YUY2, GST_VIDEO_FORMAT_YV12, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, FALSE, FALSE, 0, 0, convert_YUY2_I420},
  {GST_VIDEO_FORMAT_YUY2, GST_VIDEO_FORMAT_Y42B, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_YUY2_Y42B},
  {GST_VIDEO_FORMAT_YUY2, GST_VIDEO_FORMAT_Y444, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_YUY2_Y444},
  {GST_VIDEO_FORMAT_UYVY, GST_VIDEO_FORMAT_GRAY8, TRUE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_UYVY_GRAY8},

  {GST_VIDEO_FORMAT_UYVY, GST_VIDEO_FORMAT_I420, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, FALSE, FALSE, 0, 0, convert_UYVY_I420},
  {GST_VIDEO_FORMAT_UYVY, GST_VIDEO_FORMAT_YV12, TRUE, FALSE, TRUE, FALSE,
      FALSE, FALSE, FALSE, FALSE, 0, 0, convert_UYVY_I420},
  {GST_VIDEO_FORMAT_UYVY, GST_VIDEO_FORMAT_Y42B, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_UYVY_Y42B},
  {GST_VIDEO_FORMAT_UYVY, GST_VIDEO_FORMAT_Y444, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_UYVY_Y444},

  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_I420, FALSE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 1, 1, convert_AYUV_I420},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 1, 1, convert_AYUV_I420},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_Y42B, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 1, 0, convert_AYUV_Y42B},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_Y444, TRUE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_AYUV_Y444},

  /* planar -> planar */
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y41B, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y42B, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_Y444, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, TRUE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_A420, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YUV9, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_I420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_YV12, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_Y41B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_Y42B, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_Y444, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_GRAY8, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_A420, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, TRUE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_YUV9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_YVU9, GST_VIDEO_FORMAT_YVU9, FALSE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  /* sempiplanar -> semiplanar */
  {GST_VIDEO_FORMAT_NV12, GST_VIDEO_FORMAT_NV12, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_NV12, GST_VIDEO_FORMAT_NV16, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_NV12, GST_VIDEO_FORMAT_NV24, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_NV21, GST_VIDEO_FORMAT_NV21, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_NV16, GST_VIDEO_FORMAT_NV12, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_NV16, GST_VIDEO_FORMAT_NV16, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_NV16, GST_VIDEO_FORMAT_NV24, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_NV61, GST_VIDEO_FORMAT_NV61, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_NV24, GST_VIDEO_FORMAT_NV12, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_NV24, GST_VIDEO_FORMAT_NV16, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_NV24, GST_VIDEO_FORMAT_NV24, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_ARGB, TRUE, TRUE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_AYUV_ARGB},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_BGRA, TRUE, TRUE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_AYUV_BGRA},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_xRGB, TRUE, TRUE, TRUE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_AYUV_ARGB},    /* alias */
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_BGRx, TRUE, TRUE, TRUE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_AYUV_BGRA},    /* alias */
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_ABGR, TRUE, TRUE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_AYUV_ABGR},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_RGBA, TRUE, TRUE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_AYUV_RGBA},
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_xBGR, TRUE, TRUE, TRUE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_AYUV_ABGR},    /* alias */
  {GST_VIDEO_FORMAT_AYUV, GST_VIDEO_FORMAT_RGBx, TRUE, TRUE, TRUE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_AYUV_RGBA},    /* alias */
#endif

  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_BGRA, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_BGRA},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_BGRx, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_BGRA},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_BGRA, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_BGRA},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_BGRx, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_BGRA},

  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_ARGB, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_xRGB, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_ARGB, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_xRGB, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_ARGB},

  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_ABGR, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_xBGR, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_RGBA, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_RGBx, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_RGB, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_BGR, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_RGB15, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_BGR15, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_RGB16, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_BGR16, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},

  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_ABGR, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_xBGR, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_RGBA, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_RGBx, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_RGB, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_BGR, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_RGB15, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_BGR15, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_RGB16, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},
  {GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_BGR16, FALSE, TRUE, TRUE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_I420_pack_ARGB},

  /* scalers */
  {GST_VIDEO_FORMAT_GBR, GST_VIDEO_FORMAT_GBR, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_YVYU, GST_VIDEO_FORMAT_YVYU, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_RGB15, GST_VIDEO_FORMAT_RGB15, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_RGB16, GST_VIDEO_FORMAT_RGB16, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_BGR15, GST_VIDEO_FORMAT_BGR15, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_BGR16, GST_VIDEO_FORMAT_BGR16, TRUE, FALSE, FALSE, TRUE,
      TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_RGB, GST_VIDEO_FORMAT_RGB, TRUE, FALSE, FALSE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_BGR, GST_VIDEO_FORMAT_BGR, TRUE, FALSE, FALSE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_v308, GST_VIDEO_FORMAT_v308, TRUE, FALSE, FALSE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_IYU2, GST_VIDEO_FORMAT_IYU2, TRUE, FALSE, FALSE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_ARGB, GST_VIDEO_FORMAT_ARGB, TRUE, FALSE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_xRGB, GST_VIDEO_FORMAT_xRGB, TRUE, FALSE, FALSE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_ABGR, GST_VIDEO_FORMAT_ABGR, TRUE, FALSE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_xBGR, GST_VIDEO_FORMAT_xBGR, TRUE, FALSE, FALSE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_RGBA, GST_VIDEO_FORMAT_RGBA, TRUE, FALSE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_RGBx, GST_VIDEO_FORMAT_RGBx, TRUE, FALSE, FALSE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_BGRA, GST_VIDEO_FORMAT_BGRA, TRUE, FALSE, FALSE, TRUE, TRUE,
      TRUE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_BGRx, GST_VIDEO_FORMAT_BGRx, TRUE, FALSE, FALSE, TRUE, TRUE,
      FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_ARGB64, GST_VIDEO_FORMAT_ARGB64, TRUE, FALSE, FALSE, TRUE,
      TRUE, TRUE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_AYUV64, GST_VIDEO_FORMAT_AYUV64, TRUE, FALSE, FALSE, TRUE,
      TRUE, TRUE, FALSE, FALSE, 0, 0, convert_scale_planes},

  {GST_VIDEO_FORMAT_GRAY16_LE, GST_VIDEO_FORMAT_GRAY16_LE, TRUE, FALSE, FALSE,
      TRUE, TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
  {GST_VIDEO_FORMAT_GRAY16_BE, GST_VIDEO_FORMAT_GRAY16_BE, TRUE, FALSE, FALSE,
      TRUE, TRUE, FALSE, FALSE, FALSE, 0, 0, convert_scale_planes},
};

static gboolean
video_converter_lookup_fastpath (GstVideoConverter * convert)
{
  int i;
  GstVideoFormat in_format, out_format;
  GstVideoTransferFunction in_transf, out_transf;
  gboolean interlaced, same_matrix, same_primaries, same_size, crop, border;
  gboolean need_copy, need_set, need_mult;
  gint width, height;

  width = GST_VIDEO_INFO_WIDTH (&convert->in_info);
  height = GST_VIDEO_INFO_HEIGHT (&convert->in_info);

  if (GET_OPT_DITHER_QUANTIZATION (convert) != 1)
    return FALSE;

  /* we don't do gamma conversion in fastpath */
  in_transf = convert->in_info.colorimetry.transfer;
  out_transf = convert->out_info.colorimetry.transfer;

  same_size = (width == convert->out_width && height == convert->out_height);

  /* fastpaths don't do gamma */
  if (CHECK_GAMMA_REMAP (convert) && (!same_size || in_transf != out_transf))
    return FALSE;

  need_copy = (convert->alpha_mode & ALPHA_MODE_COPY) == ALPHA_MODE_COPY;
  need_set = (convert->alpha_mode & ALPHA_MODE_SET) == ALPHA_MODE_SET;
  need_mult = (convert->alpha_mode & ALPHA_MODE_MULT) == ALPHA_MODE_MULT;
  GST_DEBUG ("alpha copy %d, set %d, mult %d", need_copy, need_set, need_mult);

  in_format = GST_VIDEO_INFO_FORMAT (&convert->in_info);
  out_format = GST_VIDEO_INFO_FORMAT (&convert->out_info);

  if (CHECK_MATRIX_NONE (convert)) {
    same_matrix = TRUE;
  } else {
    GstVideoColorMatrix in_matrix, out_matrix;

    in_matrix = convert->in_info.colorimetry.matrix;
    out_matrix = convert->out_info.colorimetry.matrix;
    same_matrix = in_matrix == out_matrix;
  }

  if (CHECK_PRIMARIES_NONE (convert)) {
    same_primaries = TRUE;
  } else {
    GstVideoColorPrimaries in_primaries, out_primaries;

    in_primaries = convert->in_info.colorimetry.primaries;
    out_primaries = convert->out_info.colorimetry.primaries;
    same_primaries = in_primaries == out_primaries;
  }

  interlaced = GST_VIDEO_INFO_IS_INTERLACED (&convert->in_info);
  interlaced |= GST_VIDEO_INFO_IS_INTERLACED (&convert->out_info);

  crop = convert->in_x || convert->in_y
      || convert->in_width < convert->in_maxwidth
      || convert->in_height < convert->in_maxheight;
  border = convert->out_x || convert->out_y
      || convert->out_width < convert->out_maxwidth
      || convert->out_height < convert->out_maxheight;

  for (i = 0; i < sizeof (transforms) / sizeof (transforms[0]); i++) {
    if (transforms[i].in_format == in_format &&
        transforms[i].out_format == out_format &&
        (transforms[i].keeps_interlaced || !interlaced) &&
        (transforms[i].needs_color_matrix || (same_matrix && same_primaries))
        && (!transforms[i].keeps_size || same_size)
        && (transforms[i].width_align & width) == 0
        && (transforms[i].height_align & height) == 0
        && (transforms[i].do_crop || !crop)
        && (transforms[i].do_border || !border)
        && (transforms[i].alpha_copy || !need_copy)
        && (transforms[i].alpha_set || !need_set)
        && (transforms[i].alpha_mult || !need_mult)) {
      guint j;

      GST_DEBUG ("using fastpath");
      if (transforms[i].needs_color_matrix)
        video_converter_compute_matrix (convert);
      convert->convert = transforms[i].convert;

      convert->tmpline =
          g_new (guint16 *, convert->conversion_runner->n_threads);
      for (j = 0; j < convert->conversion_runner->n_threads; j++)
        convert->tmpline[j] = g_malloc0 (sizeof (guint16) * (width + 8) * 4);

      if (!transforms[i].keeps_size)
        if (!setup_scale (convert))
          return FALSE;
      if (border)
        setup_borderline (convert);
      return TRUE;
    }
  }
  GST_DEBUG ("no fastpath found");
  return FALSE;
}
