Add Interplay MVE format demuxer/decoder and muxer/encoder. Demuxer doesn't support...
authorJens Granseuer <jensgr@gmx.net>
Thu, 11 Jan 2007 11:39:56 +0000 (11:39 +0000)
committerTim-Philipp Müller <tim@centricular.net>
Thu, 11 Jan 2007 11:39:56 +0000 (11:39 +0000)
Original commit message from CVS:
Patch by: Jens Granseuer  <jensgr at gmx net>
* configure.ac:
* gst/mve/Makefile.am:
* gst/mve/TODO:
* gst/mve/gstmve.c:
* gst/mve/gstmvedemux.c:
* gst/mve/gstmvedemux.h:
* gst/mve/gstmvemux.c:
* gst/mve/gstmvemux.h:
* gst/mve/mve.h:
* gst/mve/mveaudiodec.c:
* gst/mve/mveaudioenc.c:
* gst/mve/mvevideodec16.c:
* gst/mve/mvevideodec8.c:
* gst/mve/mvevideoenc16.c:
* gst/mve/mvevideoenc8.c:
Add Interplay MVE format demuxer/decoder and muxer/encoder. Demuxer
doesn't support seeking yet, but seems to work fine otherwise.
Closes #348973.

16 files changed:
ChangeLog
configure.ac
gst/mve/Makefile.am [new file with mode: 0644]
gst/mve/TODO [new file with mode: 0644]
gst/mve/gstmve.c [new file with mode: 0644]
gst/mve/gstmvedemux.c [new file with mode: 0644]
gst/mve/gstmvedemux.h [new file with mode: 0644]
gst/mve/gstmvemux.c [new file with mode: 0644]
gst/mve/gstmvemux.h [new file with mode: 0644]
gst/mve/mve.h [new file with mode: 0644]
gst/mve/mveaudiodec.c [new file with mode: 0644]
gst/mve/mveaudioenc.c [new file with mode: 0644]
gst/mve/mvevideodec16.c [new file with mode: 0644]
gst/mve/mvevideodec8.c [new file with mode: 0644]
gst/mve/mvevideoenc16.c [new file with mode: 0644]
gst/mve/mvevideoenc8.c [new file with mode: 0644]

index 478a618..626561b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+2007-01-11  Tim-Philipp Müller  <tim at centricular dot net>
+
+       Patch by: Jens Granseuer  <jensgr at gmx net>
+
+       * configure.ac:
+       * gst/mve/Makefile.am:
+       * gst/mve/TODO:
+       * gst/mve/gstmve.c:
+       * gst/mve/gstmvedemux.c:
+       * gst/mve/gstmvedemux.h:
+       * gst/mve/gstmvemux.c:
+       * gst/mve/gstmvemux.h:
+       * gst/mve/mve.h:
+       * gst/mve/mveaudiodec.c:
+       * gst/mve/mveaudioenc.c:
+       * gst/mve/mvevideodec16.c:
+       * gst/mve/mvevideodec8.c:
+       * gst/mve/mvevideoenc16.c:
+       * gst/mve/mvevideoenc8.c:
+         Add Interplay MVE format demuxer/decoder and muxer/encoder. Demuxer
+         doesn't support seeking yet, but seems to work fine otherwise.
+         Closes #348973.
+
 2007-01-09  Tim-Philipp Müller  <tim at centricular dot net>
 
        * gst/real/Makefile.am:
index ac0bf2c..3790a6f 100644 (file)
@@ -86,6 +86,7 @@ GST_PLUGINS_ALL="\
   nuvdemux \
   modplug \
   multifile \
+  mve \
   nsf \
   replaygain \
   spectrum \
@@ -924,10 +925,11 @@ gst/h264parse/Makefile
 gst/interleave/Makefile
 gst/librfb/Makefile
 gst/modplug/Makefile
-gst/nuvdemux/Makefile
 gst/modplug/libmodplug/Makefile
 gst/multifile/Makefile
+gst/mve/Makefile
 gst/nsf/Makefile
+gst/nuvdemux/Makefile
 gst/replaygain/Makefile
 gst/spectrum/Makefile
 gst/speed/Makefile
diff --git a/gst/mve/Makefile.am b/gst/mve/Makefile.am
new file mode 100644 (file)
index 0000000..3b1a5a4
--- /dev/null
@@ -0,0 +1,20 @@
+plugin_LTLIBRARIES = libgstmve.la
+
+libgstmve_la_CFLAGS = $(GST_BASE_CFLAGS) $(GST_CFLAGS)
+libgstmve_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS)
+libgstmve_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+
+libgstmve_la_SOURCES = \
+       gstmve.c \
+       gstmvemux.c \
+       gstmvedemux.c \
+       mveaudiodec.c \
+       mvevideodec8.c \
+       mvevideodec16.c \
+       mveaudioenc.c \
+       mvevideoenc8.c \
+       mvevideoenc16.c
+
+noinst_HEADERS = gstmvedemux.h gstmvemux.h mve.h
+
+EXTRA_DIST = TODO
diff --git a/gst/mve/TODO b/gst/mve/TODO
new file mode 100644 (file)
index 0000000..4384f3e
--- /dev/null
@@ -0,0 +1,5 @@
+MVE TODO:
+ - seeking support
+ - split out decoders from demuxer into separate elements
+ - split out encoders from muxer into separate elements
+
diff --git a/gst/mve/gstmve.c b/gst/mve/gstmve.c
new file mode 100644 (file)
index 0000000..ae1014b
--- /dev/null
@@ -0,0 +1,46 @@
+/* GStreamer plugin for Interplay MVE movie files
+ *
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * For more information about the Interplay MVE format, visit:
+ *   http://www.pcisys.net/~melanson/codecs/interplay-mve.txt
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "gstmvedemux.h"
+#include "gstmvemux.h"
+
+static gboolean
+mve_plugin_init (GstPlugin * plugin)
+{
+
+  return gst_element_register (plugin, "mvedemux",
+      GST_RANK_PRIMARY,
+      GST_TYPE_MVE_DEMUX) &&
+      gst_element_register (plugin, "mvemux", GST_RANK_NONE, GST_TYPE_MVE_MUX);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    "mve",
+    "Interplay MVE movie format manipulation",
+    mve_plugin_init,
+    VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
diff --git a/gst/mve/gstmvedemux.c b/gst/mve/gstmvedemux.c
new file mode 100644 (file)
index 0000000..767e730
--- /dev/null
@@ -0,0 +1,1126 @@
+/* GStreamer demultiplexer plugin for Interplay MVE movie files
+ *
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * For more information about the Interplay MVE format, visit:
+ *   http://www.pcisys.net/~melanson/codecs/interplay-mve.txt
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <string.h>
+#include "gstmvedemux.h"
+#include "mve.h"
+
+GST_DEBUG_CATEGORY_STATIC (mvedemux_debug);
+#define GST_CAT_DEFAULT mvedemux_debug
+
+extern int ipvideo_decode_frame8 (const GstMveDemuxStream * s,
+    const unsigned char *data, unsigned short len);
+extern int ipvideo_decode_frame16 (const GstMveDemuxStream * s,
+    const unsigned char *data, unsigned short len);
+
+extern void ipaudio_uncompress (short *buffer,
+    unsigned short buf_len, const unsigned char *data, unsigned char channels);
+
+enum MveDemuxState
+{
+  MVEDEMUX_STATE_INITIAL,       /* initial state, header not read */
+  MVEDEMUX_STATE_NEXT_CHUNK,    /* parsing chunk/segment header */
+  MVEDEMUX_STATE_MOVIE,         /* reading the stream */
+  MVEDEMUX_STATE_SKIP           /* skipping chunk */
+};
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-mve")
+    );
+
+static GstStaticPadTemplate vidsrc_template = GST_STATIC_PAD_TEMPLATE ("video",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("video/x-raw-rgb, "
+        "width = (int) [ 1, MAX ], "
+        "height = (int) [ 1, MAX ], "
+        "framerate = (fraction) [ 0, MAX ], "
+        "bpp = (int) 16, "
+        "depth = (int) 15, "
+        "endianness = (int) BYTE_ORDER, "
+        "red_mask = (int) 31744, "
+        "green_mask = (int) 992, "
+        "blue_mask = (int) 31; "
+        "video/x-raw-rgb, "
+        "width = (int) [ 1, MAX ], "
+        "height = (int) [ 1, MAX ], "
+        "framerate = (fraction) [ 0, MAX ], "
+        "bpp = (int) 8, " "depth = (int) 8, " "endianness = (int) BYTE_ORDER")
+    );
+
+static GstStaticPadTemplate audsrc_template = GST_STATIC_PAD_TEMPLATE ("audio",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("audio/x-raw-int, "
+        "width = (int) 8, "
+        "rate = (int) [ 1, MAX ], "
+        "channels = (int) [ 1, 2 ], "
+        "depth = (int) 8, "
+        "signed = (boolean) false; "
+        "audio/x-raw-int, "
+        "width = (int) 16, "
+        "rate = (int) [ 1, MAX ], "
+        "channels = (int) [ 1, 2 ], "
+        "depth = (int) 16, "
+        "signed = (boolean) true, "
+        "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }")
+    );
+
+#define MVE_DEFAULT_AUDIO_STREAM 0x01
+
+static void gst_mve_demux_class_init (GstMveDemuxClass * klass);
+static void gst_mve_demux_base_init (GstMveDemuxClass * klass);
+static void gst_mve_demux_init (GstMveDemux * mve);
+
+#define GST_MVE_SEGMENT_SIZE(data)    (GST_READ_UINT16_LE (data))
+#define GST_MVE_SEGMENT_TYPE(data)    (GST_READ_UINT8 (data + 2))
+#define GST_MVE_SEGMENT_VERSION(data) (GST_READ_UINT8 (data + 3))
+
+static GstElementClass *parent_class = NULL;
+
+static void
+gst_mve_demux_reset (GstMveDemux * mve)
+{
+  gst_adapter_clear (mve->adapter);
+
+  if (mve->video_stream != NULL) {
+    if (mve->video_stream->pad)
+      gst_element_remove_pad (GST_ELEMENT (mve), mve->video_stream->pad);
+    if (mve->video_stream->caps)
+      gst_caps_unref (mve->video_stream->caps);
+    if (mve->video_stream->palette)
+      gst_buffer_unref (mve->video_stream->palette);
+    g_free (mve->video_stream->code_map);
+    if (mve->video_stream->buffer)
+      gst_buffer_unref (mve->video_stream->buffer);
+    g_free (mve->video_stream);
+    mve->video_stream = NULL;
+  }
+
+  if (mve->audio_stream != NULL) {
+    if (mve->audio_stream->pad)
+      gst_element_remove_pad (GST_ELEMENT (mve), mve->audio_stream->pad);
+    if (mve->audio_stream->caps)
+      gst_caps_unref (mve->audio_stream->caps);
+    if (mve->audio_stream->buffer)
+      gst_buffer_unref (mve->audio_stream->buffer);
+    g_free (mve->audio_stream);
+    mve->audio_stream = NULL;
+  }
+
+  mve->state = MVEDEMUX_STATE_INITIAL;
+  mve->needed_bytes = MVE_PREAMBLE_SIZE;
+  mve->frame_duration = GST_CLOCK_TIME_NONE;
+
+  mve->chunk_size = 0;
+  mve->chunk_offset = 0;
+}
+
+static const GstQueryType *
+gst_mve_demux_get_src_query_types (GstPad * pad)
+{
+  static const GstQueryType src_types[] = {
+    GST_QUERY_POSITION,
+    0
+  };
+
+  return src_types;
+}
+
+static gboolean
+gst_mve_demux_handle_src_query (GstPad * pad, GstQuery * query)
+{
+  gboolean res = FALSE;
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_POSITION:{
+      GstFormat format;
+
+      gst_query_parse_position (query, &format, NULL);
+
+      /* we only support TIME */
+      if (format == GST_FORMAT_TIME) {
+        GstMveDemuxStream *s = gst_pad_get_element_private (pad);
+
+        if (s != NULL) {
+          GST_OBJECT_LOCK (s);
+          gst_query_set_position (query, GST_FORMAT_TIME, s->last_ts);
+          GST_OBJECT_UNLOCK (s);
+          res = TRUE;
+        }
+      }
+      break;
+    }
+    default:
+      res = gst_pad_query_default (pad, query);
+      break;
+  }
+
+  return res;
+}
+
+static GstStateChangeReturn
+gst_mve_demux_change_state (GstElement * element, GstStateChange transition)
+{
+  GstMveDemux *mve = GST_MVE_DEMUX (element);
+
+  if (GST_ELEMENT_CLASS (parent_class)->change_state) {
+    GstStateChangeReturn ret;
+
+    ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+    if (ret != GST_STATE_CHANGE_SUCCESS)
+      return ret;
+  }
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_mve_demux_reset (mve);
+      break;
+    default:
+      break;
+  }
+
+  return GST_STATE_CHANGE_SUCCESS;
+}
+
+static gboolean
+gst_mve_add_stream (GstMveDemux * mve, GstMveDemuxStream * stream,
+    GstTagList * list)
+{
+  GstPadTemplate *templ;
+  gboolean ret = FALSE;
+
+  if (stream->pad == NULL) {
+    if (stream == mve->video_stream) {
+      templ = gst_static_pad_template_get (&vidsrc_template);
+      stream->pad = gst_pad_new_from_template (templ, "video");
+    } else {
+      templ = gst_static_pad_template_get (&audsrc_template);
+      stream->pad = gst_pad_new_from_template (templ, "audio");
+    }
+    gst_object_unref (templ);
+
+    gst_pad_set_query_type_function (stream->pad,
+        GST_DEBUG_FUNCPTR (gst_mve_demux_get_src_query_types));
+    gst_pad_set_query_function (stream->pad,
+        GST_DEBUG_FUNCPTR (gst_mve_demux_handle_src_query));
+    gst_pad_set_element_private (stream->pad, stream);
+
+    GST_DEBUG_OBJECT (mve, "adding pad %s", GST_PAD_NAME (stream->pad));
+    gst_pad_set_active (stream->pad, TRUE);
+    gst_element_add_pad (GST_ELEMENT (mve), stream->pad);
+    ret = TRUE;
+  }
+
+  GST_DEBUG_OBJECT (mve, "setting caps %" GST_PTR_FORMAT, stream->caps);
+  gst_pad_set_caps (stream->pad, stream->caps);
+
+  if (list)
+    gst_element_found_tags_for_pad (GST_ELEMENT (mve), stream->pad, list);
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_mve_stream_error (GstMveDemux * mve, guint16 req, guint16 avail)
+{
+  GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+      ("wanted to read %d bytes from stream, %d available", req, avail));
+  return GST_FLOW_ERROR;
+}
+
+static GstFlowReturn
+gst_mve_buffer_alloc_for_pad (GstMveDemuxStream * stream,
+    guint32 size, GstBuffer ** buffer)
+{
+  GstFlowReturn ret =
+      gst_pad_alloc_buffer_and_set_caps (stream->pad, stream->offset,
+      size, stream->caps, buffer);
+
+  if (ret == GST_FLOW_OK)
+    GST_BUFFER_TIMESTAMP (*buffer) = stream->last_ts;
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_mve_video_init (GstMveDemux * mve, const guint8 * data)
+{
+  GST_DEBUG_OBJECT (mve, "init video");
+
+  if (mve->video_stream == NULL) {
+    GstMveDemuxStream *stream = g_new0 (GstMveDemuxStream, 1);
+
+    stream->buffer = NULL;
+    stream->back_buf1 = NULL;
+    stream->back_buf2 = NULL;
+    stream->offset = 0;
+    stream->width = 0;
+    stream->height = 0;
+    stream->code_map = NULL;
+    stream->code_map_avail = FALSE;
+    stream->palette = NULL;
+    stream->caps = NULL;
+    stream->last_ts = GST_CLOCK_TIME_NONE;
+    mve->video_stream = stream;
+  }
+
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_mve_video_create_buffer (GstMveDemux * mve, guint8 version,
+    const guint8 * data, guint16 len)
+{
+  GstBuffer *buf;
+  guint16 w, h, n, true_color, bpp;
+  guint required, size;
+
+  GST_DEBUG_OBJECT (mve, "create video buffer");
+
+  if (mve->video_stream == NULL) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("trying to create video buffer for uninitialized stream"));
+    return GST_FLOW_ERROR;
+  }
+
+  /* need 4 to 8 more bytes */
+  required = (version > 1) ? 8 : (version * 2);
+  if (len < required)
+    return gst_mve_stream_error (mve, required, len);
+
+  w = GST_READ_UINT16_LE (data) << 3;
+  h = GST_READ_UINT16_LE (data + 2) << 3;
+
+  if (version > 0)
+    n = GST_READ_UINT16_LE (data + 4);
+  else
+    n = 1;
+
+  if (version > 1)
+    true_color = GST_READ_UINT16_LE (data + 6);
+  else
+    true_color = 0;
+
+  bpp = (true_color ? 2 : 1);
+  size = w * h * bpp;
+
+  if (mve->video_stream->buffer != NULL) {
+    GST_DEBUG_OBJECT (mve, "video buffer already created");
+
+    if (GST_BUFFER_SIZE (mve->video_stream->buffer) == size * 2)
+      return GST_FLOW_OK;
+
+    GST_DEBUG_OBJECT (mve, "video buffer size has changed");
+    gst_buffer_unref (mve->video_stream->buffer);
+  }
+
+  GST_DEBUG_OBJECT (mve,
+      "allocating video buffer, w:%ld, h:%ld, n:%ld, true_color:%ld", w, h, n,
+      true_color);
+
+  /* we need a buffer to keep the last 2 frames, since those may be
+     needed for decoding the next one */
+  buf = gst_buffer_new_and_alloc (size * 2);
+
+  mve->video_stream->bpp = bpp;
+  mve->video_stream->width = w;
+  mve->video_stream->height = h;
+  mve->video_stream->buffer = buf;
+  mve->video_stream->back_buf1 = GST_BUFFER_DATA (buf);
+  mve->video_stream->back_buf2 = mve->video_stream->back_buf1 + size;
+  mve->video_stream->max_block_offset = (h - 7) * w - 8;
+  memset (mve->video_stream->back_buf1, 0, size * 2);
+
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_mve_video_palette (GstMveDemux * mve, const guint8 * data, guint16 len)
+{
+  GstBuffer *buf;
+  guint16 start, count;
+  const guint8 *pal;
+  guint32 *pal_ptr;
+  gint i;
+
+  GST_DEBUG_OBJECT (mve, "video palette");
+
+  if (mve->video_stream == NULL) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("found palette before video stream was initialized"));
+    return GST_FLOW_ERROR;
+  }
+
+  /* need 4 more bytes now, more later */
+  if (len < 4)
+    return gst_mve_stream_error (mve, 4, len);
+
+  len -= 4;
+
+  start = GST_READ_UINT16_LE (data);
+  count = GST_READ_UINT16_LE (data + 2);
+  GST_DEBUG_OBJECT (mve, "found palette start:%ld, count:%ld", start, count);
+
+  /* need more bytes */
+  if (len < count * 3)
+    return gst_mve_stream_error (mve, count * 3, len);
+
+  /* make sure we don't exceed the buffer */
+  if (start + count > MVE_PALETTE_COUNT) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("palette too large for buffer"));
+    return GST_FLOW_ERROR;
+  }
+
+  if (mve->video_stream->palette != NULL) {
+    /* older buffers floating around might still use the old
+       palette, so make sure we can update it */
+    buf = gst_buffer_make_writable (mve->video_stream->palette);
+  } else {
+    buf = gst_buffer_new_and_alloc (MVE_PALETTE_COUNT * 4);
+    memset (GST_BUFFER_DATA (buf), 0, GST_BUFFER_SIZE (buf));
+  }
+
+  mve->video_stream->palette = buf;
+
+  pal = data + 4;
+  pal_ptr = ((guint32 *) GST_BUFFER_DATA (buf)) + start;
+  for (i = 0; i < count; ++i) {
+    /* convert from 6-bit VGA to 8-bit palette */
+    guint8 r, g, b;
+
+    r = (*pal) << 2;
+    ++pal;
+    g = (*pal) << 2;
+    ++pal;
+    b = (*pal) << 2;
+    ++pal;
+    *pal_ptr = (r << 16) | (g << 8) | (b);
+    ++pal_ptr;
+  }
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_mve_video_palette_compressed (GstMveDemux * mve, const guint8 * data,
+    guint16 len)
+{
+  guint8 mask;
+  gint i, j;
+  guint32 *col;
+
+  GST_DEBUG_OBJECT (mve, "compressed video palette");
+
+  if (mve->video_stream == NULL) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("found palette before video stream was initialized"));
+    return GST_FLOW_ERROR;
+  }
+
+  if (mve->video_stream->palette == NULL) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("no palette available for modification"));
+    return GST_FLOW_ERROR;
+  }
+
+  /* need at least 32 more bytes */
+  if (len < 32)
+    return gst_mve_stream_error (mve, 32, len);
+
+  len -= 32;
+
+  for (i = 0; i < 32; ++i) {
+    mask = GST_READ_UINT8 (data);
+    ++data;
+
+    if (mask != 0) {
+      for (j = 0; j < 8; ++j) {
+        if (mask & (1 << j)) {
+          guint8 r, g, b;
+
+          /* need 3 more bytes */
+          if (len < 3)
+            return gst_mve_stream_error (mve, 3, len);
+
+          len -= 3;
+
+          r = (*data) << 2;
+          ++data;
+          g = (*data) << 2;
+          ++data;
+          b = (*data) << 2;
+          ++data;
+          col =
+              ((guint32 *) GST_BUFFER_DATA (mve->video_stream->palette)) +
+              i * 8 + j;
+          *col = (r << 16) | (g << 8) | (b);
+        }
+      }
+    }
+  }
+
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_mve_video_code_map (GstMveDemux * mve, const guint8 * data, guint16 len)
+{
+  gint min;
+
+  if (mve->video_stream == NULL || mve->video_stream->code_map == NULL) {
+    GST_WARNING_OBJECT (mve, "video stream not initialized");
+    return GST_FLOW_ERROR;
+  }
+
+  GST_DEBUG_OBJECT (mve, "found code map, size:%ld", len);
+
+  /* decoding is done in 8x8 blocks using 4-bit opcodes */
+  min = (mve->video_stream->width * mve->video_stream->height) / (8 * 8 * 2);
+
+  if (len < min)
+    return gst_mve_stream_error (mve, min, len);
+
+  memcpy (mve->video_stream->code_map, data, min);
+  mve->video_stream->code_map_avail = TRUE;
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_mve_video_data (GstMveDemux * mve, const guint8 * data, guint16 len,
+    GstBuffer ** output)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  gint16 cur_frame, last_frame;
+  gint16 x_offset, y_offset;
+  gint16 x_size, y_size;
+  guint16 flags;
+  gint dec;
+  GstBuffer *buf = NULL;
+  GstMveDemuxStream *s = mve->video_stream;
+
+  GST_LOG_OBJECT (mve, "video data");
+
+  if (s == NULL) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("trying to decode video data before stream was initialized"));
+    return GST_FLOW_ERROR;
+  }
+
+  if (GST_CLOCK_TIME_IS_VALID (mve->frame_duration)) {
+    if (GST_CLOCK_TIME_IS_VALID (s->last_ts))
+      s->last_ts += mve->frame_duration;
+    else
+      s->last_ts = 0;
+  }
+
+  if (!s->code_map_avail) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("no code map available for decoding"));
+    return GST_FLOW_ERROR;
+  }
+
+  /* need at least 14 more bytes */
+  if (len < 14)
+    return gst_mve_stream_error (mve, 14, len);
+
+  len -= 14;
+
+  cur_frame = GST_READ_UINT16_LE (data);
+  last_frame = GST_READ_UINT16_LE (data + 2);
+  x_offset = GST_READ_UINT16_LE (data + 4);
+  y_offset = GST_READ_UINT16_LE (data + 6);
+  x_size = GST_READ_UINT16_LE (data + 8);
+  y_size = GST_READ_UINT16_LE (data + 10);
+  flags = GST_READ_UINT16_LE (data + 12);
+  data += 14;
+
+  GST_DEBUG_OBJECT (mve,
+      "video data hot:%d, cold:%d, xoff:%d, yoff:%d, w:%d, h:%d, flags:%x",
+      cur_frame, last_frame, x_offset, y_offset, x_size, y_size, flags);
+
+  if (flags & MVE_VIDEO_DELTA_FRAME) {
+    guint8 *temp = s->back_buf1;
+
+    s->back_buf1 = s->back_buf2;
+    s->back_buf2 = temp;
+  }
+
+  ret = gst_mve_buffer_alloc_for_pad (s, s->width * s->height * s->bpp, &buf);
+  if (ret != GST_FLOW_OK)
+    return ret;
+
+  if (s->bpp == 2) {
+    dec = ipvideo_decode_frame16 (s, data, len);
+  } else {
+    if (s->palette == NULL) {
+      GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL), ("no palette available"));
+      goto error;
+    }
+
+    dec = ipvideo_decode_frame8 (s, data, len);
+  }
+  if (dec != 0)
+    goto error;
+
+  memcpy (GST_BUFFER_DATA (buf), s->back_buf1, GST_BUFFER_SIZE (buf));
+  GST_BUFFER_DURATION (buf) = mve->frame_duration;
+  GST_BUFFER_OFFSET_END (buf) = ++s->offset;
+
+  if (s->bpp == 1) {
+    GstCaps *caps;
+
+    /* set the palette on the outgoing buffer */
+    caps = gst_caps_copy (s->caps);
+    gst_caps_set_simple (caps,
+        "palette_data", GST_TYPE_BUFFER, s->palette, NULL);
+    gst_buffer_set_caps (buf, caps);
+    gst_caps_unref (caps);
+  }
+
+  *output = buf;
+  return GST_FLOW_OK;
+
+error:
+  gst_buffer_unref (buf);
+  return GST_FLOW_ERROR;
+}
+
+static GstFlowReturn
+gst_mve_audio_init (GstMveDemux * mve, guint8 version, const guint8 * data,
+    guint16 len)
+{
+  GstMveDemuxStream *stream;
+  guint16 flags;
+  guint32 requested_buffer;
+  GstTagList *list;
+  gchar *name;
+
+  GST_DEBUG_OBJECT (mve, "init audio");
+
+  /* need 8 more bytes */
+  if (len < 8)
+    return gst_mve_stream_error (mve, 8, len);
+
+  if (mve->audio_stream == NULL) {
+    stream = g_new0 (GstMveDemuxStream, 1);
+    stream->offset = 0;
+    stream->last_ts = 0;
+    mve->audio_stream = stream;
+  } else {
+    stream = mve->audio_stream;
+    gst_caps_unref (stream->caps);
+  }
+
+  flags = GST_READ_UINT16_LE (data + 2);
+  stream->sample_rate = GST_READ_UINT16_LE (data + 4);
+  requested_buffer = GST_READ_UINT32_LE (data + 6);
+
+  /* bit 0: 0 = mono, 1 = stereo */
+  stream->n_channels = (flags & MVE_AUDIO_STEREO) + 1;
+  /* bit 1: 0 = 8 bit, 1 = 16 bit */
+  stream->sample_size = (((flags & MVE_AUDIO_16BIT) >> 1) + 1) * 8;
+  /* bit 2: 0 = uncompressed, 1 = compressed */
+  stream->compression = ((version > 0) && (flags & MVE_AUDIO_COMPRESSED)) ?
+      TRUE : FALSE;
+
+  GST_DEBUG_OBJECT (mve, "audio init, sample_rate:%ld, channels:%ld, "
+      "bits_per_sample:%ld, compression:%ld, buffer:%ld",
+      stream->sample_rate, stream->n_channels,
+      stream->sample_size, stream->compression, requested_buffer);
+
+  stream->caps = gst_caps_from_string ("audio/x-raw-int");
+  if (stream->caps == NULL)
+    return GST_FLOW_ERROR;
+
+  gst_caps_set_simple (stream->caps,
+      "signed", G_TYPE_BOOLEAN, (stream->sample_size == 8) ? FALSE : TRUE,
+      "depth", G_TYPE_INT, stream->sample_size,
+      "width", G_TYPE_INT, stream->sample_size,
+      "channels", G_TYPE_INT, stream->n_channels,
+      "rate", G_TYPE_INT, stream->sample_rate, NULL);
+  if (stream->sample_size > 8) {
+    /* for uncompressed audio we can simply copy the incoming buffer
+       which is always in little endian format */
+    gst_caps_set_simple (stream->caps, "endianness", G_TYPE_INT,
+        (stream->compression ? G_BYTE_ORDER : LITTLE_ENDIAN), NULL);
+  } else if (stream->compression) {
+    GST_WARNING_OBJECT (mve,
+        "compression is only supported for 16-bit samples");
+    stream->compression = FALSE;
+  }
+
+  list = gst_tag_list_new ();
+  name = g_strdup_printf ("Raw %d-bit PCM audio", stream->sample_size);
+  gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
+      GST_TAG_AUDIO_CODEC, name, NULL);
+  g_free (name);
+
+  if (gst_mve_add_stream (mve, stream, list))
+    return gst_pad_push_event (mve->audio_stream->pad,
+        gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME,
+            0, GST_CLOCK_TIME_NONE, 0));
+  else
+    return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_mve_audio_data (GstMveDemux * mve, guint8 type, const guint8 * data,
+    guint16 len, GstBuffer ** output)
+{
+  GstFlowReturn ret;
+  GstMveDemuxStream *s = mve->audio_stream;
+  GstBuffer *buf = NULL;
+  guint16 stream_mask;
+  guint16 size;
+
+  GST_LOG_OBJECT (mve, "audio data");
+
+  if (s == NULL) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("trying to queue samples with no audio stream"));
+    return GST_FLOW_ERROR;
+  }
+
+  /* need at least 6 more bytes */
+  if (len < 6)
+    return gst_mve_stream_error (mve, 6, len);
+
+  len -= 6;
+
+  stream_mask = GST_READ_UINT16_LE (data + 2);
+  size = GST_READ_UINT16_LE (data + 4);
+  data += 6;
+
+  if (stream_mask & MVE_DEFAULT_AUDIO_STREAM) {
+    guint16 n_samples = size / s->n_channels / (s->sample_size / 8);
+    GstClockTime duration = (GST_SECOND / s->sample_rate) * n_samples;
+
+    if (type == 8) {
+      guint16 required = (s->compression ? size / 2 + s->n_channels : size);
+
+      if (len < required)
+        return gst_mve_stream_error (mve, required, len);
+
+      ret = gst_mve_buffer_alloc_for_pad (s, size, &buf);
+
+      if (ret != GST_FLOW_OK)
+        return ret;
+
+      if (s->compression)
+        ipaudio_uncompress ((gint16 *) GST_BUFFER_DATA (buf), size,
+            data, s->n_channels);
+      else
+        memcpy (GST_BUFFER_DATA (buf), data, size);
+
+      GST_BUFFER_DURATION (buf) = duration;
+      GST_BUFFER_OFFSET_END (buf) = s->offset + n_samples;
+
+      GST_DEBUG_OBJECT (mve, "created audio buffer, size:%ld, stream_mask:%lx",
+          size, stream_mask);
+
+      *output = buf;
+    } else {
+      /* silence -
+         don't return a buffer but notify downstream there won't be
+         any data in this chunk */
+      if (mve->audio_stream->pad)
+        gst_pad_push_event (mve->audio_stream->pad,
+            gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME,
+                s->last_ts + duration, GST_CLOCK_TIME_NONE, 0));
+    }
+
+    s->offset += n_samples;
+    s->last_ts += duration;
+  } else {
+    /* alternate audio streams not supported.
+       are there any movies which use them? */
+    if (type == 8)
+      GST_WARNING_OBJECT (mve, "found non-empty alternate audio stream");
+  }
+
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_mve_timer_create (GstMveDemux * mve, const guint8 * data, guint16 len,
+    GstBuffer ** buf)
+{
+  guint32 t_rate;
+  guint16 t_subdiv;
+  GstMveDemuxStream *s;
+  GstTagList *list;
+  gint rate_nom, rate_den;
+
+  g_return_val_if_fail (mve->video_stream != NULL, GST_FLOW_ERROR);
+
+  /* need 6 more bytes */
+  if (len < 6)
+    return gst_mve_stream_error (mve, 6, len);
+
+  t_rate = GST_READ_UINT32_LE (data);
+  t_subdiv = GST_READ_UINT16_LE (data + 4);
+
+  GST_DEBUG_OBJECT (mve, "found timer:%ldx%d", t_rate, t_subdiv);
+  mve->frame_duration = t_rate * t_subdiv * GST_USECOND;
+
+  /* now really start rolling... */
+  s = mve->video_stream;
+
+  if ((s->buffer == NULL) || (s->width == 0) || (s->height == 0)) {
+    GST_ELEMENT_ERROR (mve, STREAM, DECODE, (NULL),
+        ("missing or invalid create-video-buffer segment (%dx%d)",
+            s->width, s->height));
+    return GST_FLOW_ERROR;
+  }
+
+  if (s->pad != NULL) {
+    if (s->caps != NULL) {
+      gst_caps_unref (s->caps);
+      s->caps = NULL;
+    }
+    if (s->code_map != NULL) {
+      g_free (s->code_map);
+      s->code_map = NULL;
+    }
+    list = NULL;
+  } else {
+    list = gst_tag_list_new ();
+    gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
+        GST_TAG_VIDEO_CODEC, "Raw RGB video", NULL);
+  }
+
+  s->caps = gst_caps_from_string ("video/x-raw-rgb");
+  if (s->caps == NULL)
+    return GST_FLOW_ERROR;
+
+  rate_nom = GST_SECOND / GST_USECOND;
+  rate_den = mve->frame_duration / GST_USECOND;
+
+  gst_caps_set_simple (s->caps,
+      "bpp", G_TYPE_INT, s->bpp * 8,
+      "depth", G_TYPE_INT, (s->bpp == 1) ? 8 : 15,
+      "width", G_TYPE_INT, s->width,
+      "height", G_TYPE_INT, s->height,
+      "framerate", GST_TYPE_FRACTION, rate_nom, rate_den,
+      "endianness", G_TYPE_INT, G_BYTE_ORDER, NULL);
+  if (s->bpp > 1) {
+    gst_caps_set_simple (s->caps, "red_mask", G_TYPE_INT, 0x7C00,       /* 31744 */
+        "green_mask", G_TYPE_INT, 0x03E0,       /*   992 */
+        "blue_mask", G_TYPE_INT, 0x001F,        /*    31 */
+        NULL);
+  }
+
+  s->code_map = g_malloc ((s->width * s->height) / (8 * 8 * 2));
+
+  if (gst_mve_add_stream (mve, s, list))
+    return gst_pad_push_event (s->pad,
+        gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME,
+            0, GST_CLOCK_TIME_NONE, 0));
+  else
+    return GST_FLOW_OK;
+}
+
+static void
+gst_mve_end_chunk (GstMveDemux * mve)
+{
+  GST_LOG_OBJECT (mve, "end of chunk");
+
+  if (mve->video_stream != NULL)
+    mve->video_stream->code_map_avail = FALSE;
+}
+
+/* parse segment */
+static GstFlowReturn
+gst_mve_parse_segment (GstMveDemux * mve, GstMveDemuxStream ** stream,
+    GstBuffer ** send)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  const guint8 *buffer, *data;
+  guint8 type, version;
+  guint16 len;
+
+  buffer = gst_adapter_peek (mve->adapter, mve->needed_bytes);
+
+  type = GST_MVE_SEGMENT_TYPE (buffer);
+
+  /* check whether to handle the segment */
+  if (type < 32) {
+    version = GST_MVE_SEGMENT_VERSION (buffer);
+    len = GST_MVE_SEGMENT_SIZE (buffer);
+    data = buffer + 4;
+
+    switch (type) {
+
+      case MVE_OC_END_OF_CHUNK:
+        gst_mve_end_chunk (mve);
+        break;
+      case MVE_OC_CREATE_TIMER:
+        ret = gst_mve_timer_create (mve, data, len, send);
+        *stream = mve->audio_stream;
+        break;
+      case MVE_OC_AUDIO_BUFFERS:
+        ret = gst_mve_audio_init (mve, version, data, len);
+        break;
+      case MVE_OC_VIDEO_BUFFERS:
+        ret = gst_mve_video_create_buffer (mve, version, data, len);
+        break;
+      case MVE_OC_AUDIO_DATA:
+      case MVE_OC_AUDIO_SILENCE:
+        ret = gst_mve_audio_data (mve, type, data, len, send);
+        *stream = mve->audio_stream;
+        break;
+      case MVE_OC_VIDEO_MODE:
+        ret = gst_mve_video_init (mve, data);
+        break;
+      case MVE_OC_PALETTE:
+        ret = gst_mve_video_palette (mve, data, len);
+        break;
+      case MVE_OC_PALETTE_COMPRESSED:
+        ret = gst_mve_video_palette_compressed (mve, data, len);
+        break;
+      case MVE_OC_CODE_MAP:
+        ret = gst_mve_video_code_map (mve, data, len);
+        break;
+      case MVE_OC_VIDEO_DATA:
+        ret = gst_mve_video_data (mve, data, len, send);
+        *stream = mve->video_stream;
+        break;
+
+      case MVE_OC_END_OF_STREAM:
+      case MVE_OC_PLAY_AUDIO:
+      case MVE_OC_PLAY_VIDEO:
+        /* these are chunks we don't need to handle */
+        GST_LOG_OBJECT (mve, "ignored segment type:0x%02x, version:0x%02x",
+            type, version);
+        break;
+      case 0x13:               /* ??? */
+      case 0x14:               /* ??? */
+      case 0x15:               /* ??? */
+        /* these are chunks we know exist but we don't care about */
+        GST_DEBUG_OBJECT (mve,
+            "known but unhandled segment type:0x%02x, version:0x%02x", type,
+            version);
+        break;
+      default:
+        GST_WARNING_OBJECT (mve,
+            "unhandled segment type:0x%02x, version:0x%02x", type, version);
+        break;
+    }
+  }
+
+  gst_adapter_flush (mve->adapter, mve->needed_bytes);
+  return ret;
+}
+
+static GstFlowReturn
+gst_mve_demux_chain (GstPad * sinkpad, GstBuffer * inbuf)
+{
+  GstMveDemux *mve = GST_MVE_DEMUX (GST_PAD_PARENT (sinkpad));
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  gst_adapter_push (mve->adapter, inbuf);
+
+  GST_DEBUG_OBJECT (mve, "queuing buffer, needed:%ld, available:%ld",
+      mve->needed_bytes, gst_adapter_available (mve->adapter));
+
+  while ((gst_adapter_available (mve->adapter) >= mve->needed_bytes) &&
+      (ret == GST_FLOW_OK)) {
+    GstMveDemuxStream *stream = NULL;
+    GstBuffer *outbuf = NULL;
+
+    switch (mve->state) {
+      case MVEDEMUX_STATE_INITIAL:
+        gst_adapter_flush (mve->adapter, mve->needed_bytes);
+
+        mve->chunk_offset += mve->needed_bytes;
+        mve->needed_bytes = 4;
+        mve->state = MVEDEMUX_STATE_NEXT_CHUNK;
+        break;
+
+      case MVEDEMUX_STATE_NEXT_CHUNK:{
+        const guint8 *data;
+        guint16 size;
+
+        data = gst_adapter_peek (mve->adapter, mve->needed_bytes);
+        size = GST_MVE_SEGMENT_SIZE (data);
+
+        if (mve->chunk_offset >= mve->chunk_size) {
+          /* new chunk, flush buffer and proceed with next segment */
+          guint16 chunk_type = GST_READ_UINT16_LE (data + 2);
+
+          gst_adapter_flush (mve->adapter, mve->needed_bytes);
+          mve->chunk_size = size;
+          mve->chunk_offset = 0;
+
+          if (chunk_type > MVE_CHUNK_END) {
+            GST_WARNING_OBJECT (mve,
+                "skipping unknown chunk type 0x%02x of size:%u", chunk_type,
+                size);
+            mve->needed_bytes += size;
+            mve->state = MVEDEMUX_STATE_SKIP;
+          } else {
+            GST_DEBUG_OBJECT (mve, "found new chunk type 0x%02x of size:%u",
+                chunk_type, size);
+          }
+        } else if (mve->chunk_offset <= mve->chunk_size) {
+          /* new segment */
+          GST_DEBUG_OBJECT (mve, "found segment type 0x%02x of size:%u",
+              GST_MVE_SEGMENT_TYPE (data), size);
+
+          mve->needed_bytes += size;
+          mve->state = MVEDEMUX_STATE_MOVIE;
+        }
+      }
+        break;
+
+      case MVEDEMUX_STATE_MOVIE:
+        ret = gst_mve_parse_segment (mve, &stream, &outbuf);
+
+        if ((ret == GST_FLOW_OK) && (outbuf != NULL)) {
+          /* send buffer */
+          GST_DEBUG_OBJECT (mve,
+              "pushing buffer with time %" GST_TIME_FORMAT
+              " (%ld bytes) on pad %s",
+              GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
+              GST_BUFFER_SIZE (outbuf), GST_PAD_NAME (stream->pad));
+
+          ret = gst_pad_push (stream->pad, outbuf);
+        }
+
+        if (!GST_FLOW_IS_FATAL (ret))
+          ret = GST_FLOW_OK;
+
+        /* update current offset */
+        mve->chunk_offset += mve->needed_bytes;
+
+        mve->state = MVEDEMUX_STATE_NEXT_CHUNK;
+        mve->needed_bytes = 4;
+        break;
+
+      case MVEDEMUX_STATE_SKIP:
+        mve->chunk_offset += mve->needed_bytes;
+        gst_adapter_flush (mve->adapter, mve->needed_bytes);
+        mve->state = MVEDEMUX_STATE_NEXT_CHUNK;
+        mve->needed_bytes = 4;
+        break;
+
+      default:
+        GST_ERROR_OBJECT (mve, "invalid state: %d", mve->state);
+        break;
+    }
+  }
+
+  return ret;
+}
+
+static void
+gst_mve_demux_dispose (GObject * obj)
+{
+  GstMveDemux *mve = GST_MVE_DEMUX (obj);
+
+  if (mve->adapter) {
+    g_object_unref (mve->adapter);
+    mve->adapter = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+static void
+gst_mve_demux_base_init (GstMveDemuxClass * klass)
+{
+  static const GstElementDetails mve_demux_details = {
+    "MVE Demuxer",
+    "Codec/Demuxer",
+    "Demultiplex an Interplay movie (MVE) stream into audio and video",
+    "Jens Granseuer <jensgr@gmx.net>"
+  };
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&sink_template));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&vidsrc_template));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&audsrc_template));
+  gst_element_class_set_details (element_class, &mve_demux_details);
+}
+
+static void
+gst_mve_demux_class_init (GstMveDemuxClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_mve_demux_dispose);
+
+  element_class->change_state = GST_DEBUG_FUNCPTR (gst_mve_demux_change_state);
+}
+
+static void
+gst_mve_demux_init (GstMveDemux * mve)
+{
+  mve->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
+  gst_pad_set_chain_function (mve->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_mve_demux_chain));
+  gst_element_add_pad (GST_ELEMENT (mve), mve->sinkpad);
+
+  mve->adapter = gst_adapter_new ();
+  gst_mve_demux_reset (mve);
+}
+
+GType
+gst_mve_demux_get_type (void)
+{
+  static GType plugin_type = 0;
+
+  if (!plugin_type) {
+    static const GTypeInfo plugin_info = {
+      sizeof (GstMveDemuxClass),
+      (GBaseInitFunc) gst_mve_demux_base_init,
+      NULL,
+      (GClassInitFunc) gst_mve_demux_class_init,
+      NULL,
+      NULL,
+      sizeof (GstMveDemux),
+      0,
+      (GInstanceInitFunc) gst_mve_demux_init,
+    };
+
+    GST_DEBUG_CATEGORY_INIT (mvedemux_debug, "mvedemux",
+        0, "Interplay MVE movie demuxer");
+
+    plugin_type = g_type_register_static (GST_TYPE_ELEMENT,
+        "GstMveDemux", &plugin_info, 0);
+  }
+  return plugin_type;
+}
diff --git a/gst/mve/gstmvedemux.h b/gst/mve/gstmvedemux.h
new file mode 100644 (file)
index 0000000..533d023
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * GStreamer demultiplexer plugin for Interplay MVE movie files
+ *
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_MVE_DEMUX_H__
+#define __GST_MVE_DEMUX_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MVE_DEMUX \
+  (gst_mve_demux_get_type())
+#define GST_MVE_DEMUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MVE_DEMUX,GstMveDemux))
+#define GST_MVE_DEMUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MVE_DEMUX,GstMveDemuxClass))
+#define GST_IS_MVE_DEMUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MVE_DEMUX))
+#define GST_IS_MVE_DEMUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MVE_DEMUX))
+
+typedef struct _GstMveDemux       GstMveDemux;
+typedef struct _GstMveDemuxClass  GstMveDemuxClass;
+typedef struct _GstMveDemuxStream GstMveDemuxStream;
+
+struct _GstMveDemux
+{
+  GstElement element;
+
+  GstPad *sinkpad;
+
+  GstMveDemuxStream *video_stream;
+  GstMveDemuxStream *audio_stream;
+
+  gint state;
+
+  /* time per frame (1/framerate) */
+  GstClockTime frame_duration;
+
+  /* push based variables */
+  guint16 needed_bytes;
+  GstAdapter *adapter;
+  
+  /* size of current chunk */
+  guint32 chunk_size;
+  /* offset in current chunk */
+  guint32 chunk_offset;
+};
+
+struct _GstMveDemuxClass 
+{
+  GstElementClass parent_class;
+};
+
+struct _GstMveDemuxStream {
+  /* shared properties */
+  GstCaps *caps;
+  GstPad *pad;
+  GstClockTime last_ts;
+  gint64 offset;
+
+  /* video properties */
+  guint16 width;
+  guint16 height;
+  guint8 bpp;   /* bytes per pixel */
+  guint8 *code_map;
+  gboolean code_map_avail;
+  guint8 *back_buf1;
+  guint8 *back_buf2;
+  guint32 max_block_offset;
+  GstBuffer *palette;
+  GstBuffer *buffer;
+
+  /* audio properties */
+  guint16 sample_rate;
+  guint16 n_channels;
+  guint16 sample_size;
+  gboolean compression;
+};
+
+GType gst_mve_demux_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_MVE_DEMUX_H__ */
diff --git a/gst/mve/gstmvemux.c b/gst/mve/gstmvemux.c
new file mode 100644 (file)
index 0000000..d66833f
--- /dev/null
@@ -0,0 +1,1493 @@
+/* Interplay MVE multiplexer plugin for GStreamer
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+gst-launch-0.10 filesrc location=movie.mve ! mvedemux name=d !
+    video/x-raw-rgb ! mvemux quick=true name=m !
+    filesink location=test.mve d. ! audio/x-raw-int ! m.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gst/gst.h>
+#include "gstmvemux.h"
+#include "mve.h"
+
+GST_DEBUG_CATEGORY_STATIC (mvemux_debug);
+#define GST_CAT_DEFAULT mvemux_debug
+
+extern GstFlowReturn mve_encode_frame8 (GstMveMux * mve,
+    GstBuffer * frame, const guint32 * palette, guint16 max_data);
+extern GstFlowReturn mve_encode_frame16 (GstMveMux * mve,
+    GstBuffer * frame, guint16 max_data);
+extern gint mve_compress_audio (guint8 * dest,
+    const guint8 * src, guint16 len, guint8 channels);
+
+static const char mve_preamble[] = MVE_PREAMBLE;
+
+enum
+{
+  ARG_0,
+  ARG_AUDIO_COMPRESSION,
+  ARG_VIDEO_QUICK_ENCODING,
+  ARG_VIDEO_SCREEN_WIDTH,
+  ARG_VIDEO_SCREEN_HEIGHT
+};
+
+#define MVE_MUX_DEFAULT_COMPRESSION    FALSE
+#define MVE_MUX_DEFAULT_SCREEN_WIDTH   640
+#define MVE_MUX_DEFAULT_SCREEN_HEIGHT  480
+
+enum MveMuxState
+{
+  MVE_MUX_STATE_INITIAL,        /* initial state */
+  MVE_MUX_STATE_CONNECTED,      /* linked, caps set, header not written */
+  MVE_MUX_STATE_PREBUFFER,      /* prebuffering audio data */
+  MVE_MUX_STATE_MOVIE,          /* writing the movie */
+  MVE_MUX_STATE_EOS
+};
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-mve")
+    );
+
+static GstStaticPadTemplate video_sink_factory =
+    GST_STATIC_PAD_TEMPLATE ("video",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS ("video/x-raw-rgb, "
+        "width = (int) [ 24, 1600 ], "
+        "height = (int) [ 24, 1200 ], "
+        "framerate = (fraction) [ 1, MAX ], "
+        "bpp = (int) 16, "
+        "depth = (int) 15, "
+        "endianness = (int) BYTE_ORDER, "
+        "red_mask = (int) 31744, "
+        "green_mask = (int) 992, "
+        "blue_mask = (int) 31; "
+        "video/x-raw-rgb, "
+        "bpp = (int) 8, "
+        "depth = (int) 8, "
+        "width = (int) [ 24, 1600 ], "
+        "height = (int) [ 24, 1200 ], "
+        "framerate = (fraction) [ 1, MAX ], " "endianness = (int) BYTE_ORDER"));
+
+static GstStaticPadTemplate audio_sink_factory =
+    GST_STATIC_PAD_TEMPLATE ("audio",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS ("audio/x-raw-int, "
+        "width = (int) 8, "
+        "rate = (int) [ 1, MAX ], "
+        "channels = (int) [ 1, 2 ], "
+        "depth = (int) 8, "
+        "signed = (boolean) false; "
+        "audio/x-raw-int, "
+        "width = (int) 16, "
+        "rate = (int) [ 1, MAX ], "
+        "channels = (int) [ 1, 2 ], "
+        "depth = (int) 16, "
+        "signed = (boolean) true, " "endianness = (int) BYTE_ORDER"));
+
+static void gst_mve_mux_base_init (GstMveMuxClass * klass);
+static void gst_mve_mux_class_init (GstMveMuxClass * klass);
+static void gst_mve_mux_init (GstMveMux * mvemux);
+
+static GstElementClass *parent_class = NULL;
+
+static void
+gst_mve_mux_reset (GstMveMux * mvemux)
+{
+  mvemux->state = MVE_MUX_STATE_INITIAL;
+  mvemux->stream_time = 0;
+  mvemux->stream_offset = 0;
+  mvemux->timer = 0;
+
+  mvemux->frame_duration = GST_CLOCK_TIME_NONE;
+  mvemux->width = 0;
+  mvemux->height = 0;
+  mvemux->screen_width = MVE_MUX_DEFAULT_SCREEN_WIDTH;
+  mvemux->screen_height = MVE_MUX_DEFAULT_SCREEN_HEIGHT;
+  mvemux->bpp = 0;
+  mvemux->video_frames = 0;
+  mvemux->pal_changed = FALSE;
+  mvemux->pal_first_color = 0;
+  mvemux->pal_colors = MVE_PALETTE_COUNT;
+  mvemux->quick_encoding = TRUE;
+
+  mvemux->bps = 0;
+  mvemux->rate = 0;
+  mvemux->channels = 0;
+  mvemux->compression = MVE_MUX_DEFAULT_COMPRESSION;
+  mvemux->next_ts = 0;
+  mvemux->max_ts = 0;
+  mvemux->spf = 0;
+  mvemux->lead_frames = 0;
+  mvemux->audio_frames = 0;
+
+  mvemux->chunk_has_palette = FALSE;
+  mvemux->chunk_has_audio = FALSE;
+
+  mvemux->audio_pad_eos = TRUE;
+  mvemux->video_pad_eos = TRUE;
+
+  g_free (mvemux->chunk_code_map);
+  mvemux->chunk_code_map = NULL;
+
+  if (mvemux->chunk_video != NULL) {
+    g_byte_array_free (mvemux->chunk_video, TRUE);
+    mvemux->chunk_video = NULL;
+  }
+
+  if (mvemux->chunk_audio != NULL) {
+    g_byte_array_free (mvemux->chunk_audio, TRUE);
+    mvemux->chunk_audio = NULL;
+  }
+
+  if (mvemux->last_frame != NULL) {
+    gst_buffer_unref (mvemux->last_frame);
+    mvemux->last_frame = NULL;
+  }
+
+  if (mvemux->second_last_frame != NULL) {
+    gst_buffer_unref (mvemux->second_last_frame);
+    mvemux->second_last_frame = NULL;
+  }
+
+  if (mvemux->audio_buffer != NULL) {
+    g_queue_foreach (mvemux->audio_buffer, (GFunc) gst_mini_object_unref, NULL);
+    g_queue_free (mvemux->audio_buffer);
+  }
+  mvemux->audio_buffer = g_queue_new ();
+
+  if (mvemux->video_buffer != NULL) {
+    g_queue_foreach (mvemux->video_buffer, (GFunc) gst_mini_object_unref, NULL);
+    g_queue_free (mvemux->video_buffer);
+  }
+  mvemux->video_buffer = g_queue_new ();
+}
+
+static void
+gst_mve_mux_pad_link (GstPad * pad, GstPad * peer, gpointer data)
+{
+  GstMveMux *mvemux = GST_MVE_MUX (data);
+
+  if (pad == mvemux->audiosink) {
+    mvemux->audio_pad_connected = TRUE;
+  } else if (pad == mvemux->videosink) {
+    mvemux->video_pad_connected = TRUE;
+  } else {
+    g_assert_not_reached ();
+  }
+
+  GST_DEBUG_OBJECT (mvemux, "pad '%s' connected", GST_PAD_NAME (pad));
+}
+
+static void
+gst_mve_mux_pad_unlink (GstPad * pad, GstPad * peer, gpointer data)
+{
+  GstMveMux *mvemux = GST_MVE_MUX (data);
+
+  if (pad == mvemux->audiosink) {
+    mvemux->audio_pad_connected = FALSE;
+  } else if (pad == mvemux->videosink) {
+    mvemux->video_pad_connected = FALSE;
+  } else {
+    g_assert_not_reached ();
+  }
+
+  GST_DEBUG_OBJECT (mvemux, "pad '%s' unlinked", GST_PAD_NAME (pad));
+}
+
+static void
+gst_mve_mux_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GstMveMux *mvemux;
+
+  g_return_if_fail (GST_IS_MVE_MUX (object));
+  mvemux = GST_MVE_MUX (object);
+
+  switch (prop_id) {
+    case ARG_AUDIO_COMPRESSION:
+      g_value_set_boolean (value, mvemux->compression);
+      break;
+    case ARG_VIDEO_QUICK_ENCODING:
+      g_value_set_boolean (value, mvemux->quick_encoding);
+      break;
+    case ARG_VIDEO_SCREEN_WIDTH:
+      g_value_set_uint (value, mvemux->screen_width);
+      break;
+    case ARG_VIDEO_SCREEN_HEIGHT:
+      g_value_set_uint (value, mvemux->screen_height);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_mve_mux_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  GstMveMux *mvemux;
+
+  g_return_if_fail (GST_IS_MVE_MUX (object));
+  mvemux = GST_MVE_MUX (object);
+
+  switch (prop_id) {
+    case ARG_AUDIO_COMPRESSION:
+      mvemux->compression = g_value_get_boolean (value);
+      break;
+    case ARG_VIDEO_QUICK_ENCODING:
+      mvemux->quick_encoding = g_value_get_boolean (value);
+      break;
+    case ARG_VIDEO_SCREEN_WIDTH:
+      mvemux->screen_width = g_value_get_uint (value);
+      break;
+    case ARG_VIDEO_SCREEN_HEIGHT:
+      mvemux->screen_height = g_value_get_uint (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstStateChangeReturn
+gst_mve_mux_change_state (GstElement * element, GstStateChange transition)
+{
+  GstMveMux *mvemux;
+
+  g_return_val_if_fail (GST_IS_MVE_MUX (element), GST_STATE_CHANGE_FAILURE);
+
+  mvemux = GST_MVE_MUX (element);
+
+  if (GST_ELEMENT_CLASS (parent_class)->change_state) {
+    GstStateChangeReturn ret;
+
+    ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+    if (ret != GST_STATE_CHANGE_SUCCESS)
+      return ret;
+  }
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_mve_mux_reset (mvemux);
+      break;
+    default:
+      break;
+  }
+
+  return GST_STATE_CHANGE_SUCCESS;
+}
+
+static const GstBuffer *
+gst_mve_mux_palette_from_buffer (GstBuffer * buf)
+{
+  const GstBuffer *palette = NULL;
+  GstCaps *caps = GST_BUFFER_CAPS (buf);
+
+  if (caps != NULL) {
+    GstStructure *str = gst_caps_get_structure (caps, 0);
+    const GValue *pal = gst_structure_get_value (str, "palette_data");
+
+    if (pal != NULL) {
+      palette = gst_value_get_buffer (pal);
+      if (GST_BUFFER_SIZE (palette) < 256 * 4)
+        palette = NULL;
+    }
+  }
+  return palette;
+}
+
+static GstFlowReturn
+gst_mve_mux_palette_from_current_frame (GstMveMux * mvemux,
+    const GstBuffer ** pal)
+{
+  GstBuffer *buf = g_queue_peek_head (mvemux->video_buffer);
+
+  /* get palette from buffer */
+  *pal = gst_mve_mux_palette_from_buffer (buf);
+  if (*pal == NULL) {
+    GST_ERROR_OBJECT (mvemux, "video buffer has no palette data");
+    return GST_FLOW_ERROR;
+  }
+  return GST_FLOW_OK;
+}
+
+static void
+gst_mve_mux_palette_analyze (GstMveMux * mvemux, const GstBuffer * pal,
+    guint16 * first, guint16 * last)
+{
+  guint i;
+  guint32 *col1;
+
+  col1 = (guint32 *) GST_BUFFER_DATA (pal);
+
+  /* compare current palette against last frame */
+  if (mvemux->last_frame == NULL) {
+    /* ignore 0,0,0 entries but make sure we get
+       at least one color */
+    /* FIXME: is ignoring 0,0,0 safe? possibly depends on player impl */
+    for (i = 0; i < MVE_PALETTE_COUNT; ++i) {
+      if (col1[i] != 0) {
+        *first = i;
+        break;
+      }
+    }
+    if (i == MVE_PALETTE_COUNT) {
+      *first = *last = 0;
+    } else {
+      for (i = MVE_PALETTE_COUNT - 1; i >= 0; --i) {
+        if (col1[i] != 0) {
+          *last = i;
+          break;
+        }
+      }
+    }
+  } else {
+    const GstBuffer *last_pal;
+    guint32 *col2;
+
+    last_pal = gst_mve_mux_palette_from_buffer (mvemux->last_frame);
+
+    g_return_if_fail (last_pal != NULL);
+
+    col2 = (guint32 *) GST_BUFFER_DATA (last_pal);
+
+    for (i = 0; i < MVE_PALETTE_COUNT; ++i) {
+      if (col1[i] != col2[i]) {
+        *first = i;
+        break;
+      }
+    }
+    for (i = MVE_PALETTE_COUNT - 1; i >= 0; --i) {
+      if (col1[i] != col2[i]) {
+        *last = i;
+        break;
+      }
+    }
+  }
+
+  GST_DEBUG_OBJECT (mvemux, "palette first:%d, last:%d", *first, *last);
+}
+
+static gboolean
+gst_mve_mux_palette_changed (GstMveMux * mvemux, const GstBuffer * pal)
+{
+  const GstBuffer *last_pal;
+
+  g_return_val_if_fail (mvemux->last_frame != NULL, TRUE);
+
+  last_pal = gst_mve_mux_palette_from_buffer (mvemux->last_frame);
+  if (last_pal == NULL)
+    return TRUE;
+
+  return memcmp (GST_BUFFER_DATA (last_pal), GST_BUFFER_DATA (pal),
+      MVE_PALETTE_COUNT * 4) != 0;
+}
+
+static GstFlowReturn
+gst_mve_mux_push_buffer (GstMveMux * mvemux, GstBuffer * buffer)
+{
+  GST_BUFFER_OFFSET (buffer) = mvemux->stream_offset;
+  mvemux->stream_offset += GST_BUFFER_SIZE (buffer);
+  GST_BUFFER_OFFSET_END (buffer) = mvemux->stream_offset;
+  return gst_pad_push (mvemux->source, buffer);
+}
+
+/* returns TRUE if audio segment is complete */
+static gboolean
+gst_mve_mux_audio_data (GstMveMux * mvemux)
+{
+  gboolean complete = FALSE;
+
+  while (!complete) {
+    GstBuffer *buf;
+    GstClockTime buftime;
+    GstClockTime duration;
+    GstClockTime t_needed;
+    gint b_needed;
+    gint len;
+
+    buf = g_queue_peek_head (mvemux->audio_buffer);
+    if (buf == NULL)
+      return (mvemux->audio_pad_eos && mvemux->chunk_audio) ||
+          (mvemux->stream_time + mvemux->frame_duration < mvemux->max_ts);
+
+    buftime = GST_BUFFER_TIMESTAMP (buf);
+    duration = GST_BUFFER_DURATION (buf);
+
+    /* FIXME: adjust buffer timestamps using segment info */
+
+    /* assume continuous buffers on invalid time stamps */
+    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (buftime)))
+      buftime = mvemux->next_ts;
+
+    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (duration)))
+      duration = gst_util_uint64_scale_int (mvemux->frame_duration,
+          GST_BUFFER_SIZE (buf), mvemux->spf);
+
+    if (mvemux->chunk_audio) {
+      b_needed = mvemux->spf - mvemux->chunk_audio->len;
+      t_needed = (gint) gst_util_uint64_scale_int (mvemux->frame_duration,
+          b_needed, mvemux->spf);
+    } else {
+      b_needed = mvemux->spf;
+      t_needed = mvemux->frame_duration;
+    }
+
+    if (buftime > mvemux->next_ts + t_needed) {
+      /* future buffer - fill chunk with silence */
+      GST_DEBUG_OBJECT (mvemux, "future buffer, inserting silence");
+
+      /* if we already have a chunk started, fill it
+         otherwise we'll simply insert a silence chunk */
+      if (mvemux->chunk_audio) {
+        len = mvemux->chunk_audio->len;
+        g_byte_array_set_size (mvemux->chunk_audio, mvemux->spf);
+        memset (mvemux->chunk_audio->data + len, 0, mvemux->spf - len);
+      }
+      mvemux->next_ts += t_needed;
+      complete = TRUE;
+    } else if (buftime + duration <= mvemux->next_ts) {
+      /* past buffer - drop */
+      GST_DEBUG_OBJECT (mvemux, "dropping past buffer");
+      g_queue_pop_head (mvemux->audio_buffer);
+      gst_buffer_unref (buf);
+    } else {
+      /* our data starts somewhere in this buffer */
+      const guint8 *bufdata = GST_BUFFER_DATA (buf);
+      gint b_available = GST_BUFFER_SIZE (buf);
+      gint align = (mvemux->bps / 8) * mvemux->channels - 1;
+      gint offset;
+
+      if (mvemux->chunk_audio == NULL)
+        mvemux->chunk_audio = g_byte_array_sized_new (mvemux->spf);
+
+      if (buftime >= mvemux->next_ts) {
+        /* insert silence as necessary */
+        len = mvemux->chunk_audio->len;
+        offset = (gint) gst_util_uint64_scale_int (mvemux->spf,
+            buftime - mvemux->next_ts, mvemux->frame_duration);
+        offset = (offset + align) & ~align;
+
+        if (len < offset) {
+          g_byte_array_set_size (mvemux->chunk_audio, offset);
+          memset (mvemux->chunk_audio->data + len, 0, offset - len);
+          b_needed -= offset - len;
+          mvemux->next_ts += gst_util_uint64_scale_int (mvemux->frame_duration,
+              offset - len, mvemux->spf);
+        }
+        offset = 0;
+      } else {
+        offset = (gint) gst_util_uint64_scale_int (mvemux->spf,
+            mvemux->next_ts - buftime, mvemux->frame_duration);
+        offset = (offset + align) & ~align;
+      }
+
+      g_assert (offset <= b_available);
+
+      bufdata += offset;
+      b_available -= offset;
+      if (b_needed > b_available)
+        b_needed = b_available;
+
+      if (mvemux->bps == 8) {
+        g_byte_array_append (mvemux->chunk_audio, bufdata, b_needed);
+      } else {
+        guint i;
+        gint16 *sample = (gint16 *) bufdata;
+        guint8 s[2];
+
+        len = b_needed / 2;
+        for (i = 0; i < len; ++i) {
+          s[0] = (*sample) & 0x00FF;
+          s[1] = ((*sample) & 0xFF00) >> 8;
+          g_byte_array_append (mvemux->chunk_audio, s, 2);
+          ++sample;
+        }
+      }
+
+      mvemux->next_ts += gst_util_uint64_scale_int (mvemux->frame_duration,
+          b_needed, mvemux->spf);
+
+      if (b_available - b_needed == 0) {
+        /* consumed buffer */
+        GST_LOG_OBJECT (mvemux, "popping consumed buffer");
+        g_queue_pop_head (mvemux->audio_buffer);
+        gst_buffer_unref (buf);
+      }
+
+      complete = (mvemux->chunk_audio->len >= mvemux->spf);
+    }
+
+    if (mvemux->max_ts < mvemux->next_ts)
+      mvemux->max_ts = mvemux->next_ts;
+  }
+
+  return complete;
+}
+
+static GstFlowReturn
+gst_mve_mux_start_movie (GstMveMux * mvemux)
+{
+  GstFlowReturn res;
+  GstBuffer *buf;
+
+  GST_DEBUG_OBJECT (mvemux, "writing movie preamble");
+
+  res = gst_pad_alloc_buffer (mvemux->source, 0,
+      MVE_PREAMBLE_SIZE, GST_PAD_CAPS (mvemux->source), &buf);
+
+  if (res != GST_FLOW_OK)
+    return res;
+
+  gst_pad_push_event (mvemux->source,
+      gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
+
+  memcpy (GST_BUFFER_DATA (buf), mve_preamble, MVE_PREAMBLE_SIZE);
+  return gst_mve_mux_push_buffer (mvemux, buf);
+}
+
+static GstFlowReturn
+gst_mve_mux_end_movie (GstMveMux * mvemux)
+{
+  GstFlowReturn res;
+  GstBuffer *buf;
+  guint8 *bufdata;
+
+  GST_DEBUG_OBJECT (mvemux, "writing movie shutdown chunk");
+
+  res = gst_pad_alloc_buffer (mvemux->source, 0, 16,
+      GST_PAD_CAPS (mvemux->source), &buf);
+
+  if (res != GST_FLOW_OK)
+    return res;
+
+  bufdata = GST_BUFFER_DATA (buf);
+
+  GST_WRITE_UINT16_LE (bufdata, 8);     /* shutdown chunk */
+  GST_WRITE_UINT16_LE (bufdata + 2, MVE_CHUNK_SHUTDOWN);
+  GST_WRITE_UINT16_LE (bufdata + 4, 0); /* end movie segment */
+  bufdata[6] = MVE_OC_END_OF_STREAM;
+  bufdata[7] = 0;
+  GST_WRITE_UINT16_LE (bufdata + 8, 0); /* end chunk segment */
+  bufdata[10] = MVE_OC_END_OF_CHUNK;
+  bufdata[11] = 0;
+
+  GST_WRITE_UINT16_LE (bufdata + 12, 0);        /* end movie chunk */
+  GST_WRITE_UINT16_LE (bufdata + 14, MVE_CHUNK_END);
+
+  return gst_mve_mux_push_buffer (mvemux, buf);
+}
+
+static GstFlowReturn
+gst_mve_mux_init_video_chunk (GstMveMux * mvemux, const GstBuffer * pal)
+{
+  GstFlowReturn res;
+  GstBuffer *buf;
+  guint8 *bufdata;
+  guint16 buf_size;
+  guint16 first_col = 0, last_col = 0;
+  guint pal_size = 0;
+
+  GST_DEBUG_OBJECT (mvemux, "init-video chunk w:%d, h:%d, bpp:%d",
+      mvemux->width, mvemux->height, mvemux->bpp);
+
+  buf_size = 4;                 /* chunk header */
+  buf_size += 4 + 6;            /* init video mode segment */
+  buf_size += 4 + 8;            /* create video buffers segment */
+
+  if (mvemux->bpp == 8) {
+    g_return_val_if_fail (pal != NULL, GST_FLOW_ERROR);
+
+    /* install palette segment */
+    gst_mve_mux_palette_analyze (mvemux, pal, &first_col, &last_col);
+    pal_size = (last_col - first_col + 1) * 3;
+    buf_size += 4 + 4 + pal_size;
+  }
+
+  buf_size += 4 + 0;            /* end chunk segment */
+
+  res = gst_pad_alloc_buffer (mvemux->source, 0, buf_size,
+      GST_PAD_CAPS (mvemux->source), &buf);
+  if (res != GST_FLOW_OK)
+    return res;
+
+  bufdata = GST_BUFFER_DATA (buf);
+
+  GST_WRITE_UINT16_LE (bufdata, buf_size - 4);
+  GST_WRITE_UINT16_LE (bufdata + 2, MVE_CHUNK_INIT_VIDEO);
+
+  GST_WRITE_UINT16_LE (bufdata + 4, 6);
+  bufdata[6] = MVE_OC_VIDEO_MODE;
+  bufdata[7] = 0;
+  GST_WRITE_UINT16_LE (bufdata + 8, mvemux->screen_width);      /* screen width */
+  GST_WRITE_UINT16_LE (bufdata + 10, mvemux->screen_height);    /* screen height */
+  GST_WRITE_UINT16_LE (bufdata + 12, 0);        /* ??? - flags */
+
+  GST_WRITE_UINT16_LE (bufdata + 14, 8);
+  bufdata[16] = MVE_OC_VIDEO_BUFFERS;
+  bufdata[17] = 2;
+  GST_WRITE_UINT16_LE (bufdata + 18, mvemux->width >> 3);       /* buffer width */
+  GST_WRITE_UINT16_LE (bufdata + 20, mvemux->height >> 3);      /* buffer height */
+  GST_WRITE_UINT16_LE (bufdata + 22, 1);        /* buffer count */
+  GST_WRITE_UINT16_LE (bufdata + 24, (mvemux->bpp >> 3) - 1);   /* true color */
+
+  bufdata += 26;
+
+  if (mvemux->bpp == 8) {
+    /* TODO: check whether we really need to update the entire palette (or at all) */
+    gint i;
+    guint32 *col;
+
+    GST_DEBUG_OBJECT (mvemux, "installing palette");
+
+    GST_WRITE_UINT16_LE (bufdata, 4 + pal_size);
+    bufdata[2] = MVE_OC_PALETTE;
+    bufdata[3] = 0;
+    GST_WRITE_UINT16_LE (bufdata + 4, first_col);       /* first color index */
+    GST_WRITE_UINT16_LE (bufdata + 6, last_col - first_col + 1);        /* number of colors */
+
+    bufdata += 8;
+    col = (guint32 *) GST_BUFFER_DATA (pal);
+    for (i = first_col; i <= last_col; ++i) {
+      /* convert from 8-bit palette to 6-bit VGA */
+      guint32 rgb = col[i];
+
+      (*bufdata) = ((rgb & 0x00FF0000) >> 16) >> 2;
+      ++bufdata;
+      (*bufdata) = ((rgb & 0x0000FF00) >> 8) >> 2;
+      ++bufdata;
+      (*bufdata) = (rgb & 0x000000FF) >> 2;
+      ++bufdata;
+    }
+
+    mvemux->pal_changed = TRUE;
+    mvemux->pal_first_color = first_col;
+    mvemux->pal_colors = last_col - first_col + 1;
+  }
+
+  GST_WRITE_UINT16_LE (bufdata, 0);
+  bufdata[2] = MVE_OC_END_OF_CHUNK;
+  bufdata[3] = 0;
+
+  return gst_mve_mux_push_buffer (mvemux, buf);
+}
+
+static GstFlowReturn
+gst_mve_mux_init_audio_chunk (GstMveMux * mvemux)
+{
+  GstFlowReturn res;
+  GstBuffer *buf;
+  guint16 buf_size;
+  guint8 *bufdata;
+  guint16 flags = 0;
+  gint align;
+
+  GST_DEBUG_OBJECT (mvemux,
+      "init-audio chunk rate:%d, chan:%d, bps:%d, comp:%d", mvemux->rate,
+      mvemux->channels, mvemux->bps, mvemux->compression);
+
+  if (G_UNLIKELY (mvemux->bps == 8 && mvemux->compression)) {
+    GST_INFO_OBJECT (mvemux,
+        "compression only supported for 16-bit samples, disabling");
+    mvemux->compression = FALSE;
+  }
+
+  /* calculate sample data per frame */
+  align = (mvemux->bps / 8) * mvemux->channels;
+  mvemux->spf =
+      (guint16) (gst_util_uint64_scale_int (align * mvemux->rate,
+          mvemux->frame_duration, GST_SECOND) + align - 1) & ~(align - 1);
+
+  /* prebuffer approx. 1 second of audio data */
+  mvemux->lead_frames = align * mvemux->rate / mvemux->spf;
+  GST_DEBUG_OBJECT (mvemux, "calculated spf:%d, lead frames:%d",
+      mvemux->spf, mvemux->lead_frames);
+
+  /* chunk header + init video mode segment + end chunk segment */
+  buf_size = 4 + (4 + 10) + 4;
+
+  res = gst_pad_alloc_buffer (mvemux->source, 0, buf_size,
+      GST_PAD_CAPS (mvemux->source), &buf);
+  if (res != GST_FLOW_OK)
+    return res;
+
+  bufdata = GST_BUFFER_DATA (buf);
+
+  if (mvemux->channels == 2)
+    flags |= MVE_AUDIO_STEREO;
+  if (mvemux->bps == 16)
+    flags |= MVE_AUDIO_16BIT;
+  if (mvemux->compression)
+    flags |= MVE_AUDIO_COMPRESSED;
+
+  GST_WRITE_UINT16_LE (bufdata, buf_size - 4);
+  GST_WRITE_UINT16_LE (bufdata + 2, MVE_CHUNK_INIT_AUDIO);
+
+  GST_WRITE_UINT16_LE (bufdata + 4, 10);
+  bufdata[6] = MVE_OC_AUDIO_BUFFERS;
+  bufdata[7] = 1;
+  GST_WRITE_UINT16_LE (bufdata + 8, 0); /* ??? */
+  GST_WRITE_UINT16_LE (bufdata + 10, flags);    /* flags */
+  GST_WRITE_UINT16_LE (bufdata + 12, mvemux->rate);     /* sample rate */
+  GST_WRITE_UINT32_LE (bufdata + 14,    /* minimum audio buffer size */
+      mvemux->spf * mvemux->lead_frames);
+
+  GST_WRITE_UINT16_LE (bufdata + 18, 0);
+  bufdata[20] = MVE_OC_END_OF_CHUNK;
+  bufdata[21] = 0;
+
+  return gst_mve_mux_push_buffer (mvemux, buf);
+}
+
+static guint8 *
+gst_mve_mux_write_audio_segments (GstMveMux * mvemux, guint8 * data)
+{
+  GByteArray *chunk = mvemux->chunk_audio;
+  guint16 silent_mask;
+
+  GST_LOG_OBJECT (mvemux, "writing audio data");
+
+  /* audio data */
+  if (chunk) {
+    guint16 len = mvemux->compression ?
+        chunk->len / 2 + mvemux->channels : chunk->len;
+
+    silent_mask = 0xFFFE;
+
+    GST_WRITE_UINT16_LE (data, 6 + len);
+    data[2] = MVE_OC_AUDIO_DATA;
+    data[3] = 0;
+    GST_WRITE_UINT16_LE (data + 4, mvemux->audio_frames);       /* frame number */
+    GST_WRITE_UINT16_LE (data + 6, 0x0001);     /* stream mask */
+    GST_WRITE_UINT16_LE (data + 8, chunk->len); /* (uncompressed) data length */
+    data += 10;
+
+    if (mvemux->compression)
+      mve_compress_audio (data, chunk->data, len, mvemux->channels);
+    else
+      memcpy (data, chunk->data, chunk->len);
+    data += len;
+
+    g_byte_array_free (chunk, TRUE);
+    mvemux->chunk_audio = NULL;
+  } else
+    silent_mask = 0xFFFF;
+
+  /* audio data (silent) */
+  GST_WRITE_UINT16_LE (data, 6);
+  data[2] = MVE_OC_AUDIO_SILENCE;
+  data[3] = 0;
+  GST_WRITE_UINT16_LE (data + 4, mvemux->audio_frames++);       /* frame number */
+  GST_WRITE_UINT16_LE (data + 6, silent_mask);  /* stream mask */
+  GST_WRITE_UINT16_LE (data + 8, mvemux->spf);  /* (imaginary) data length */
+  data += 10;
+
+  return data;
+}
+
+static GstFlowReturn
+gst_mve_mux_prebuffer_audio_chunk (GstMveMux * mvemux)
+{
+  GstFlowReturn ret;
+  GstBuffer *chunk;
+  guint16 size;
+  guint8 *data;
+
+  /* calculate chunk size */
+  size = 4;                     /* chunk header */
+
+  if (mvemux->chunk_audio) {
+    size += 4 + 6 +             /* audio data */
+        (mvemux->compression ?
+        mvemux->chunk_audio->len / 2 + mvemux->channels :
+        mvemux->chunk_audio->len);
+  }
+  size += 4 + 6;                /* audio data silent */
+  size += 4;                    /* end chunk */
+
+  ret = gst_pad_alloc_buffer (mvemux->source, 0, size,
+      GST_PAD_CAPS (mvemux->source), &chunk);
+  if (ret != GST_FLOW_OK)
+    return ret;
+
+  data = GST_BUFFER_DATA (chunk);
+
+  /* assemble chunk */
+  GST_WRITE_UINT16_LE (data, size - 4);
+  GST_WRITE_UINT16_LE (data + 2, MVE_CHUNK_AUDIO_ONLY);
+  data += 4;
+
+  data = gst_mve_mux_write_audio_segments (mvemux, data);
+
+  /* end chunk */
+  GST_WRITE_UINT16_LE (data, 0);
+  data[2] = MVE_OC_END_OF_CHUNK;
+  data[3] = 0;
+
+  if (mvemux->audio_frames >= mvemux->lead_frames)
+    mvemux->state = MVE_MUX_STATE_MOVIE;
+
+  mvemux->stream_time += mvemux->frame_duration;
+
+  GST_DEBUG_OBJECT (mvemux, "pushing audio chunk");
+
+  return gst_mve_mux_push_buffer (mvemux, chunk);
+}
+
+static GstFlowReturn
+gst_mve_mux_push_chunk (GstMveMux * mvemux)
+{
+  GstFlowReturn ret;
+  GstBuffer *chunk;
+  GstBuffer *frame;
+  guint32 size;
+  guint16 cm_size = 0;
+  guint8 *data;
+
+  /* calculate chunk size */
+  size = 4;                     /* chunk header */
+
+  if (G_UNLIKELY (mvemux->timer == 0)) {
+    /* we need to insert a timer segment */
+    size += 4 + 6;
+  }
+
+  if (mvemux->audio_pad_connected) {
+    if (mvemux->chunk_audio) {
+      size += 4 + 6 +           /* audio data */
+          (mvemux->compression ?
+          mvemux->chunk_audio->len / 2 + mvemux->channels :
+          mvemux->chunk_audio->len);
+    }
+    size += 4 + 6;              /* audio data silent */
+  }
+
+  size += 4 + 6;                /* play video */
+  size += 4;                    /* play audio; present even if no audio stream */
+  size += 4;                    /* end chunk */
+
+  /* we must encode video only after we have the audio side
+     covered, since only then we can tell what size limit
+     the video data must adhere to */
+  frame = g_queue_pop_head (mvemux->video_buffer);
+  if (frame != NULL) {
+    cm_size = (((mvemux->width * mvemux->height) >> 6) + 1) >> 1;
+    size += 4 + cm_size;        /* code map */
+    size += 4 + 14;             /* video data header */
+
+    /* make sure frame is writable since the encoder may want to modify it */
+    frame = gst_buffer_make_writable (frame);
+
+    if (mvemux->bpp == 8) {
+      const GstBuffer *pal = gst_mve_mux_palette_from_buffer (frame);
+
+      if (pal == NULL)
+        ret = GST_FLOW_ERROR;
+      else
+        ret = mve_encode_frame8 (mvemux, frame,
+            (guint32 *) GST_BUFFER_DATA (pal), G_MAXUINT16 - size);
+    } else
+      ret = mve_encode_frame16 (mvemux, frame, G_MAXUINT16 - size);
+
+    if (mvemux->second_last_frame != NULL)
+      gst_buffer_unref (mvemux->second_last_frame);
+    mvemux->second_last_frame = mvemux->last_frame;
+    mvemux->last_frame = frame;
+
+    if (ret != GST_FLOW_OK)
+      return ret;
+
+    size += mvemux->chunk_video->len;
+  }
+
+  if (size > G_MAXUINT16) {
+    GST_ELEMENT_ERROR (mvemux, STREAM, ENCODE, (NULL),
+        ("encoding frame %d failed: maximum block size exceeded (%lu)",
+            mvemux->video_frames + 1, size));
+    return GST_FLOW_ERROR;
+  }
+
+  ret = gst_pad_alloc_buffer (mvemux->source, 0, size,
+      GST_PAD_CAPS (mvemux->source), &chunk);
+  if (ret != GST_FLOW_OK)
+    return ret;
+
+  data = GST_BUFFER_DATA (chunk);
+
+  /* assemble chunk */
+  GST_WRITE_UINT16_LE (data, size - 4);
+  GST_WRITE_UINT16_LE (data + 2, MVE_CHUNK_VIDEO);
+  data += 4;
+
+  if (G_UNLIKELY (mvemux->timer == 0)) {
+    /* insert a timer segment */
+    mvemux->timer = mvemux->frame_duration / GST_USECOND / 8;
+
+    GST_WRITE_UINT16_LE (data, 6);
+    data[2] = MVE_OC_CREATE_TIMER;
+    data[3] = 0;
+    GST_WRITE_UINT32_LE (data + 4, mvemux->timer);      /* timer rate */
+    GST_WRITE_UINT16_LE (data + 8, 8);  /* timer subdivision */
+    data += 10;
+  }
+
+  /* code map */
+  if (mvemux->chunk_video) {
+    GST_WRITE_UINT16_LE (data, cm_size);
+    data[2] = MVE_OC_CODE_MAP;
+    data[3] = 0;
+    memcpy (data + 4, mvemux->chunk_code_map, cm_size);
+    data += 4 + cm_size;
+  }
+
+  if (mvemux->audio_pad_connected)
+    data = gst_mve_mux_write_audio_segments (mvemux, data);
+
+  if (mvemux->chunk_video) {
+    GST_LOG_OBJECT (mvemux, "writing video data");
+
+    /* video data */
+    GST_WRITE_UINT16_LE (data, 14 + mvemux->chunk_video->len);
+    data[2] = MVE_OC_VIDEO_DATA;
+    data[3] = 0;
+    GST_WRITE_UINT16_LE (data + 6, mvemux->video_frames);       /* previous frame */
+    GST_WRITE_UINT16_LE (data + 4, ++mvemux->video_frames);     /* current frame */
+    GST_WRITE_UINT16_LE (data + 8, 0);  /* x offset */
+    GST_WRITE_UINT16_LE (data + 10, 0); /* y offset */
+    GST_WRITE_UINT16_LE (data + 12, mvemux->width >> 3);        /* buffer width */
+    GST_WRITE_UINT16_LE (data + 14, mvemux->height >> 3);       /* buffer height */
+    GST_WRITE_UINT16_LE (data + 16,     /* flags */
+        (mvemux->video_frames == 1 ? 0 : MVE_VIDEO_DELTA_FRAME));
+    memcpy (data + 18, mvemux->chunk_video->data, mvemux->chunk_video->len);
+    data += 18 + mvemux->chunk_video->len;
+
+    g_byte_array_free (mvemux->chunk_video, TRUE);
+    mvemux->chunk_video = NULL;
+  }
+
+  /* play audio */
+  GST_WRITE_UINT16_LE (data, 0);
+  data[2] = MVE_OC_PLAY_AUDIO;
+  data[3] = 0;
+  data += 4;
+
+  /* play video */
+  GST_WRITE_UINT16_LE (data, 6);
+  data[2] = MVE_OC_PLAY_VIDEO;
+  data[3] = 1;
+  /* this block is only set to non-zero on palette changes in 8-bit mode */
+  if (mvemux->pal_changed) {
+    GST_WRITE_UINT16_LE (data + 4, mvemux->pal_first_color);    /* index of first color */
+    GST_WRITE_UINT16_LE (data + 6, mvemux->pal_colors); /* number of colors */
+    mvemux->pal_changed = FALSE;
+  } else {
+    GST_WRITE_UINT32_LE (data + 4, 0);
+  }
+  GST_WRITE_UINT16_LE (data + 8, 0);    /* ??? */
+  data += 10;
+
+  /* end chunk */
+  GST_WRITE_UINT16_LE (data, 0);
+  data[2] = MVE_OC_END_OF_CHUNK;
+  data[3] = 0;
+
+  mvemux->chunk_has_palette = FALSE;
+  mvemux->chunk_has_audio = FALSE;
+  mvemux->stream_time += mvemux->frame_duration;
+
+  GST_LOG_OBJECT (mvemux, "pushing video chunk");
+
+  return gst_mve_mux_push_buffer (mvemux, chunk);
+}
+
+static GstFlowReturn
+gst_mve_mux_chain (GstPad * sinkpad, GstBuffer * inbuf)
+{
+  GstMveMux *mvemux = GST_MVE_MUX (GST_PAD_PARENT (sinkpad));
+  GstFlowReturn ret = GST_FLOW_OK;
+  const GstBuffer *palette;
+  gboolean audio_ok, video_ok;
+
+  /* need to serialize the buffers */
+  g_mutex_lock (mvemux->lock);
+
+  if (G_LIKELY (inbuf != NULL)) {       /* TODO: see _sink_event... */
+    if (sinkpad == mvemux->audiosink)
+      g_queue_push_tail (mvemux->audio_buffer, inbuf);
+    else if (sinkpad == mvemux->videosink)
+      g_queue_push_tail (mvemux->video_buffer, inbuf);
+    else
+      g_assert_not_reached ();
+  }
+
+  /* TODO: this is gross... */
+  if (G_UNLIKELY (mvemux->state == MVE_MUX_STATE_INITIAL)) {
+    GST_DEBUG_OBJECT (mvemux, "waiting for caps");
+    goto done;
+  }
+
+  /* now actually try to mux something */
+  if (G_UNLIKELY (mvemux->state == MVE_MUX_STATE_CONNECTED)) {
+    palette = NULL;
+
+    if (mvemux->bpp == 8) {
+      /* we need to add palette info to the init chunk */
+      if (g_queue_is_empty (mvemux->video_buffer))
+        goto done;              /* wait for more data */
+
+      ret = gst_mve_mux_palette_from_current_frame (mvemux, &palette);
+      if (ret != GST_FLOW_OK)
+        goto done;
+    }
+
+    gst_mve_mux_start_movie (mvemux);
+    gst_mve_mux_init_video_chunk (mvemux, palette);
+    mvemux->chunk_has_palette = TRUE;
+
+    if (mvemux->audio_pad_connected) {
+      gst_mve_mux_init_audio_chunk (mvemux);
+
+      mvemux->state = MVE_MUX_STATE_PREBUFFER;
+    } else
+      mvemux->state = MVE_MUX_STATE_MOVIE;
+  }
+
+  while ((mvemux->state == MVE_MUX_STATE_PREBUFFER) && (ret == GST_FLOW_OK) &&
+      gst_mve_mux_audio_data (mvemux)) {
+    ret = gst_mve_mux_prebuffer_audio_chunk (mvemux);
+  }
+
+  if (G_LIKELY (mvemux->state >= MVE_MUX_STATE_MOVIE)) {
+    audio_ok = !mvemux->audio_pad_connected ||
+        !g_queue_is_empty (mvemux->audio_buffer) ||
+        (mvemux->audio_pad_eos && (mvemux->stream_time <= mvemux->max_ts));
+    video_ok = !g_queue_is_empty (mvemux->video_buffer) ||
+        (mvemux->video_pad_eos &&
+        (!mvemux->audio_pad_eos || (mvemux->stream_time <= mvemux->max_ts)));
+
+    while ((ret == GST_FLOW_OK) && audio_ok && video_ok) {
+
+      if (!g_queue_is_empty (mvemux->video_buffer)) {
+        if ((mvemux->bpp == 8) && !mvemux->chunk_has_palette) {
+          ret = gst_mve_mux_palette_from_current_frame (mvemux, &palette);
+          if (ret != GST_FLOW_OK)
+            goto done;
+
+          if (gst_mve_mux_palette_changed (mvemux, palette))
+            gst_mve_mux_init_video_chunk (mvemux, palette);
+          mvemux->chunk_has_palette = TRUE;
+        }
+      }
+
+      /* audio data */
+      if (mvemux->audio_pad_connected && !mvemux->chunk_has_audio &&
+          gst_mve_mux_audio_data (mvemux))
+        mvemux->chunk_has_audio = TRUE;
+
+      if ((!g_queue_is_empty (mvemux->video_buffer) || mvemux->video_pad_eos) &&
+          (mvemux->chunk_has_audio || !mvemux->audio_pad_connected
+              || mvemux->audio_pad_eos)) {
+        ret = gst_mve_mux_push_chunk (mvemux);
+      }
+
+      audio_ok = !mvemux->audio_pad_connected ||
+          !g_queue_is_empty (mvemux->audio_buffer) ||
+          (mvemux->audio_pad_eos && (mvemux->stream_time <= mvemux->max_ts));
+      video_ok = !g_queue_is_empty (mvemux->video_buffer) ||
+          (mvemux->video_pad_eos &&
+          (!mvemux->audio_pad_eos || (mvemux->stream_time <= mvemux->max_ts)));
+    }
+  }
+
+  if (G_UNLIKELY ((mvemux->state == MVE_MUX_STATE_EOS) && (ret == GST_FLOW_OK))) {
+    ret = gst_mve_mux_end_movie (mvemux);
+    gst_pad_push_event (mvemux->source, gst_event_new_eos ());
+  }
+
+done:
+  g_mutex_unlock (mvemux->lock);
+  return ret;
+}
+
+static gboolean
+gst_mve_mux_sink_event (GstPad * pad, GstEvent * event)
+{
+  gboolean res = TRUE;
+  GstMveMux *mvemux = GST_MVE_MUX (GST_PAD_PARENT (pad));
+
+  GST_DEBUG_OBJECT (mvemux, "got %s event for pad %s",
+      GST_EVENT_TYPE_NAME (event), GST_PAD_NAME (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_EOS:
+      if (pad == mvemux->audiosink) {
+        mvemux->audio_pad_eos = TRUE;
+
+        if (mvemux->state == MVE_MUX_STATE_PREBUFFER)
+          mvemux->state = MVE_MUX_STATE_MOVIE;
+      } else if (pad == mvemux->videosink)
+        mvemux->video_pad_eos = TRUE;
+
+      /* TODO: this is evil */
+      if (mvemux->audio_pad_eos && mvemux->video_pad_eos) {
+        mvemux->state = MVE_MUX_STATE_EOS;
+        gst_mve_mux_chain (pad, NULL);
+      }
+      gst_event_unref (event);
+      break;
+    case GST_EVENT_NEWSEGMENT:
+      if (pad == mvemux->audiosink) {
+        GstFormat format;
+        gint64 start;
+        gboolean update;
+
+        gst_event_parse_new_segment (event, &update, NULL, &format, &start,
+            NULL, NULL);
+        if ((format == GST_FORMAT_TIME) && update && (start > mvemux->max_ts))
+          mvemux->max_ts = start;
+      }
+      gst_event_unref (event);
+      break;
+    default:
+      res = gst_pad_event_default (pad, event);
+      break;
+  }
+
+  return res;
+}
+
+static gboolean
+gst_mve_mux_vidsink_set_caps (GstPad * pad, GstCaps * vscaps)
+{
+  GstMveMux *mvemux;
+  GstStructure *structure;
+  GstClockTime duration;
+  const GValue *fps;
+  gint w, h, bpp;
+  gboolean ret;
+
+  mvemux = GST_MVE_MUX (GST_PAD_PARENT (pad));
+
+  GST_DEBUG_OBJECT (mvemux, "video set_caps triggered on %s",
+      GST_PAD_NAME (pad));
+
+  structure = gst_caps_get_structure (vscaps, 0);
+
+  ret = gst_structure_get_int (structure, "width", &w);
+  ret &= gst_structure_get_int (structure, "height", &h);
+  ret &= gst_structure_get_int (structure, "bpp", &bpp);
+  fps = gst_structure_get_value (structure, "framerate");
+  ret &= (fps != NULL && GST_VALUE_HOLDS_FRACTION (fps));
+
+  duration = gst_util_uint64_scale_int (GST_SECOND,
+      gst_value_get_fraction_denominator (fps),
+      gst_value_get_fraction_numerator (fps));
+
+  if (!ret)
+    return FALSE;
+
+  /* don't allow changing width, height, bpp, or framerate */
+  if (mvemux->state != MVE_MUX_STATE_INITIAL) {
+    if (mvemux->width != w || mvemux->height != h ||
+        mvemux->bpp != bpp || mvemux->frame_duration != duration) {
+      GST_ERROR_OBJECT (mvemux, "caps renegotiation not allowed");
+      return FALSE;
+    }
+  } else {
+    if (w % 8 != 0 || h % 8 != 0) {
+      GST_ERROR_OBJECT (mvemux, "width and height must be multiples of 8");
+      return FALSE;
+    }
+
+    mvemux->width = w;
+    mvemux->height = h;
+    mvemux->bpp = bpp;
+    mvemux->frame_duration = duration;
+
+    if (mvemux->screen_width < w) {
+      GST_INFO_OBJECT (mvemux, "setting suggested screen width to %d", w);
+      mvemux->screen_width = w;
+    }
+    if (mvemux->screen_height < h) {
+      GST_INFO_OBJECT (mvemux, "setting suggested screen height to %d", h);
+      mvemux->screen_height = h;
+    }
+
+    g_free (mvemux->chunk_code_map);
+    mvemux->chunk_code_map = g_malloc ((((w * h) >> 6) + 1) >> 1);
+
+    /* audio caps already initialized? */
+    if (mvemux->bps != 0 || !mvemux->audio_pad_connected)
+      mvemux->state = MVE_MUX_STATE_CONNECTED;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_mve_mux_audsink_set_caps (GstPad * pad, GstCaps * ascaps)
+{
+  GstMveMux *mvemux;
+  GstStructure *structure;
+  gboolean ret;
+  gint val;
+
+  mvemux = GST_MVE_MUX (GST_PAD_PARENT (pad));
+
+  GST_DEBUG_OBJECT (mvemux, "audio set_caps triggered on %s",
+      GST_PAD_NAME (pad));
+
+  /* don't allow caps renegotiation for now */
+  if (mvemux->state != MVE_MUX_STATE_INITIAL)
+    return FALSE;
+
+  structure = gst_caps_get_structure (ascaps, 0);
+
+  ret = gst_structure_get_int (structure, "channels", &val);
+  mvemux->channels = val;
+  ret &= gst_structure_get_int (structure, "rate", &val);
+  mvemux->rate = val;
+  ret &= gst_structure_get_int (structure, "width", &val);
+  mvemux->bps = val;
+
+  /* video caps already initialized? */
+  if (mvemux->bpp != 0)
+    mvemux->state = MVE_MUX_STATE_CONNECTED;
+
+  return ret;
+}
+
+static GstPad *
+gst_mve_mux_request_new_pad (GstElement * element,
+    GstPadTemplate * templ, const gchar * req_name)
+{
+  GstMveMux *mvemux = GST_MVE_MUX (element);
+  GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
+  GstPad *pad;
+
+  g_return_val_if_fail (templ != NULL, NULL);
+
+  if (templ->direction != GST_PAD_SINK) {
+    GST_WARNING_OBJECT (mvemux, "request pad is not a SINK pad");
+    return NULL;
+  }
+
+  if (templ == gst_element_class_get_pad_template (klass, "audio")) {
+    if (mvemux->audiosink)
+      return NULL;
+
+    mvemux->audiosink = gst_pad_new_from_template (templ, "audio");
+    gst_pad_set_setcaps_function (mvemux->audiosink,
+        GST_DEBUG_FUNCPTR (gst_mve_mux_audsink_set_caps));
+    mvemux->audio_pad_eos = FALSE;
+    pad = mvemux->audiosink;
+  } else if (templ == gst_element_class_get_pad_template (klass, "video")) {
+    if (mvemux->videosink)
+      return NULL;
+
+    mvemux->videosink = gst_pad_new_from_template (templ, "video");
+    gst_pad_set_setcaps_function (mvemux->videosink,
+        GST_DEBUG_FUNCPTR (gst_mve_mux_vidsink_set_caps));
+    mvemux->video_pad_eos = FALSE;
+    pad = mvemux->videosink;
+  } else {
+    g_assert_not_reached ();
+  }
+
+  gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_mve_mux_chain));
+  gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_mve_mux_sink_event));
+
+  g_signal_connect (pad, "linked", G_CALLBACK (gst_mve_mux_pad_link), mvemux);
+  g_signal_connect (pad, "unlinked", G_CALLBACK (gst_mve_mux_pad_unlink),
+      mvemux);
+
+  gst_element_add_pad (element, pad);
+  return pad;
+}
+
+static void
+gst_mve_mux_release_pad (GstElement * element, GstPad * pad)
+{
+  GstMveMux *mvemux = GST_MVE_MUX (element);
+
+  gst_element_remove_pad (element, pad);
+
+  if (pad == mvemux->audiosink) {
+    mvemux->audiosink = NULL;
+    mvemux->audio_pad_connected = FALSE;
+  } else if (pad == mvemux->videosink) {
+    mvemux->videosink = NULL;
+    mvemux->video_pad_connected = FALSE;
+  }
+}
+
+static void
+gst_mve_mux_base_init (GstMveMuxClass * klass)
+{
+  static const GstElementDetails gst_mve_mux_details =
+      GST_ELEMENT_DETAILS ("MVE Multiplexer",
+      "Codec/Muxer",
+      "Muxes audio and video into an MVE stream",
+      "Jens Granseuer <jensgr@gmx.net>");
+
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&src_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&audio_sink_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&video_sink_factory));
+
+  gst_element_class_set_details (element_class, &gst_mve_mux_details);
+}
+
+static void
+gst_mve_mux_finalize (GObject * object)
+{
+  GstMveMux *mvemux = GST_MVE_MUX (object);
+
+  if (mvemux->lock) {
+    g_mutex_free (mvemux->lock);
+    mvemux->lock = NULL;
+  }
+
+  if (mvemux->audio_buffer) {
+    g_queue_free (mvemux->audio_buffer);
+    mvemux->audio_buffer = NULL;
+  }
+
+  if (mvemux->video_buffer) {
+    g_queue_free (mvemux->video_buffer);
+    mvemux->video_buffer = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_mve_mux_class_init (GstMveMuxClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize = gst_mve_mux_finalize;
+
+  gobject_class->get_property = gst_mve_mux_get_property;
+  gobject_class->set_property = gst_mve_mux_set_property;
+
+  g_object_class_install_property (gobject_class, ARG_AUDIO_COMPRESSION,
+      g_param_spec_boolean ("compression", "Audio compression",
+          "Whether to compress audio data", MVE_MUX_DEFAULT_COMPRESSION,
+          G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, ARG_VIDEO_QUICK_ENCODING,
+      g_param_spec_boolean ("quick", "Quick encoding",
+          "Whether to disable expensive encoding operations", TRUE,
+          G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, ARG_VIDEO_SCREEN_WIDTH,
+      g_param_spec_uint ("screen-width", "Screen width",
+          "Suggested screen width", 320, 1600,
+          MVE_MUX_DEFAULT_SCREEN_WIDTH, G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, ARG_VIDEO_SCREEN_HEIGHT,
+      g_param_spec_uint ("screen-height", "Screen height",
+          "Suggested screen height", 200, 1200,
+          MVE_MUX_DEFAULT_SCREEN_HEIGHT, G_PARAM_READWRITE));
+
+  gstelement_class->request_new_pad = gst_mve_mux_request_new_pad;
+  gstelement_class->release_pad = gst_mve_mux_release_pad;
+
+  gstelement_class->change_state = gst_mve_mux_change_state;
+}
+
+static void
+gst_mve_mux_init (GstMveMux * mvemux)
+{
+  GstElementClass *klass = GST_ELEMENT_GET_CLASS (mvemux);
+
+  mvemux->source =
+      gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
+          "src"), "src");
+  gst_element_add_pad (GST_ELEMENT (mvemux), mvemux->source);
+
+  mvemux->lock = g_mutex_new ();
+
+  mvemux->audiosink = NULL;
+  mvemux->videosink = NULL;
+  mvemux->audio_pad_connected = FALSE;
+  mvemux->video_pad_connected = FALSE;
+
+  /* audio/video metadata initialisation */
+  mvemux->last_frame = NULL;
+  mvemux->second_last_frame = NULL;
+  mvemux->chunk_code_map = NULL;
+  mvemux->chunk_video = NULL;
+  mvemux->chunk_audio = NULL;
+  mvemux->audio_buffer = NULL;
+  mvemux->video_buffer = NULL;
+
+  gst_mve_mux_reset (mvemux);
+}
+
+GType
+gst_mve_mux_get_type (void)
+{
+  static GType mvemux_type = 0;
+
+  if (!mvemux_type) {
+    static const GTypeInfo mvemux_info = {
+      sizeof (GstMveMuxClass),
+      (GBaseInitFunc) gst_mve_mux_base_init,
+      NULL,
+      (GClassInitFunc) gst_mve_mux_class_init,
+      NULL,
+      NULL,
+      sizeof (GstMveMux),
+      0,
+      (GInstanceInitFunc) gst_mve_mux_init,
+    };
+
+    GST_DEBUG_CATEGORY_INIT (mvemux_debug, "mvemux",
+        0, "Interplay MVE movie muxer");
+
+    mvemux_type =
+        g_type_register_static (GST_TYPE_ELEMENT, "GstMveMux", &mvemux_info, 0);
+  }
+  return mvemux_type;
+}
diff --git a/gst/mve/gstmvemux.h b/gst/mve/gstmvemux.h
new file mode 100644 (file)
index 0000000..319b2ae
--- /dev/null
@@ -0,0 +1,120 @@
+/* 
+ * Interplay MVE muxer plugin for GStreamer
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_MVE_MUX_H__
+#define __GST_MVE_MUX_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MVE_MUX \
+  (gst_mve_mux_get_type())
+#define GST_MVE_MUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MVE_MUX,GstMveMux))
+#define GST_MVE_MUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MVE_MUX,GstMveMux))
+#define GST_IS_MVE_MUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MVE_MUX))
+#define GST_IS_MVE_MUX_CLASS(obj) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MVE_MUX))
+
+
+typedef struct _GstMveMux GstMveMux;
+typedef struct _GstMveMuxClass GstMveMuxClass;
+
+struct _GstMveMux {
+  GstElement element;
+  GMutex *lock;
+
+  /* pads */
+  GstPad *source;
+  GstPad *videosink;
+  GstPad *audiosink;
+
+  gboolean audio_pad_connected;
+  gboolean audio_pad_eos;
+  gboolean video_pad_connected;
+  gboolean video_pad_eos;
+
+  guint64 stream_offset;
+  /* audio stream time, really */
+  GstClockTime stream_time;
+  guint timer;
+  gint state;
+
+  /* ticks per frame */
+  GstClockTime frame_duration;
+
+  /* video stream properties */
+  guint16 width, height;
+  guint16 screen_width, screen_height;
+  /* bits per pixel */
+  guint8 bpp;
+  /* previous frames */
+  GstBuffer *last_frame;
+  GstBuffer *second_last_frame;
+  /* number of encoded frames */
+  guint16 video_frames;
+  /* palette handling */
+  gboolean pal_changed;
+  guint16 pal_first_color;
+  guint16 pal_colors;
+  /* whether to use expensive opcodes */
+  gboolean quick_encoding;
+
+  /* audio stream properties */
+  /* bits per sample */
+  guint8 bps;
+  guint32 rate;
+  guint8 channels;
+  gboolean compression;
+  /* current audio stream time */
+  GstClockTime next_ts;
+  /* maximum audio time we know about */
+  GstClockTime max_ts;
+  /* sample bytes per frame */
+  guint16 spf;
+  /* number of frames to use for audio lead-in */
+  guint16 lead_frames;
+  /* number of encoded frames */
+  guint16 audio_frames;
+
+  /* current chunk */
+  guint8 *chunk_code_map;
+  GByteArray *chunk_video;
+  GByteArray *chunk_audio;
+  gboolean chunk_has_palette;
+  gboolean chunk_has_audio;
+
+  /* buffers for incoming data */
+  GQueue *audio_buffer;
+  GQueue *video_buffer;
+};
+
+struct _GstMveMuxClass {
+  GstElementClass parent_class;
+};
+
+GType gst_mve_mux_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_MVE_MUX_H__ */
diff --git a/gst/mve/mve.h b/gst/mve/mve.h
new file mode 100644 (file)
index 0000000..782289e
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Interplay MVE movie definitions
+ *
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MVE_H__
+#define __MVE_H__
+
+#define MVE_PREAMBLE      "Interplay MVE File\032\000\032\000\000\001\063\021"
+#define MVE_PREAMBLE_SIZE 26
+
+#define MVE_PALETTE_COUNT 256
+
+/* MVE chunk types */
+#define MVE_CHUNK_INIT_AUDIO          0x0000
+#define MVE_CHUNK_AUDIO_ONLY          0x0001
+#define MVE_CHUNK_INIT_VIDEO          0x0002
+#define MVE_CHUNK_VIDEO               0x0003
+#define MVE_CHUNK_SHUTDOWN            0x0004
+#define MVE_CHUNK_END                 0x0005
+
+/* MVE segment opcodes */
+#define MVE_OC_END_OF_STREAM          0x00
+#define MVE_OC_END_OF_CHUNK           0x01
+#define MVE_OC_CREATE_TIMER           0x02
+#define MVE_OC_AUDIO_BUFFERS          0x03
+#define MVE_OC_PLAY_AUDIO             0x04
+#define MVE_OC_VIDEO_BUFFERS          0x05
+#define MVE_OC_PLAY_VIDEO             0x07
+#define MVE_OC_AUDIO_DATA             0x08
+#define MVE_OC_AUDIO_SILENCE          0x09
+#define MVE_OC_VIDEO_MODE             0x0A
+#define MVE_OC_PALETTE                0x0C
+#define MVE_OC_PALETTE_COMPRESSED     0x0D
+#define MVE_OC_CODE_MAP               0x0F
+#define MVE_OC_VIDEO_DATA             0x11
+
+/* audio flags */
+#define MVE_AUDIO_STEREO              0x0001
+#define MVE_AUDIO_16BIT               0x0002
+#define MVE_AUDIO_COMPRESSED          0x0004
+
+/* video flags */
+#define MVE_VIDEO_DELTA_FRAME         0x0001
+
+#endif /* __MVE_H__ */
diff --git a/gst/mve/mveaudiodec.c b/gst/mve/mveaudiodec.c
new file mode 100644 (file)
index 0000000..0e9ee4e
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2003 The ffmpeg Project, Mike Melanson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Interplay compressed audio codec by Mike Melanson (melanson@pcisys.net)
+ */
+
+#include <gst/gst.h>
+
+static const short delta_table[256] = {
+  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+  16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+  32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 47, 51, 56, 61,
+  66, 72, 79, 86, 94, 102, 112, 122, 133, 145, 158, 173, 189, 206, 225, 245,
+  267, 292, 318, 348, 379, 414, 452, 493, 538, 587, 640, 699, 763, 832, 908,
+  991,
+  1081, 1180, 1288, 1405, 1534, 1673, 1826, 1993, 2175, 2373, 2590, 2826, 3084,
+  3365, 3672, 4008,
+  4373, 4772, 5208, 5683, 6202, 6767, 7385, 8059, 8794, 9597, 10472, 11428,
+  12471, 13609, 14851, 16206,
+  17685, 19298, 21060, 22981, 25078, 27367, 29864, 32589, -29973, -26728,
+  -23186, -19322, -15105, -10503, -5481, -1,
+  1, 1, 5481, 10503, 15105, 19322, 23186, 26728, 29973, -32589, -29864, -27367,
+  -25078, -22981, -21060, -19298,
+  -17685, -16206, -14851, -13609, -12471, -11428, -10472, -9597, -8794, -8059,
+  -7385, -6767, -6202, -5683, -5208, -4772,
+  -4373, -4008, -3672, -3365, -3084, -2826, -2590, -2373, -2175, -1993, -1826,
+  -1673, -1534, -1405, -1288, -1180,
+  -1081, -991, -908, -832, -763, -699, -640, -587, -538, -493, -452, -414, -379,
+  -348, -318, -292,
+  -267, -245, -225, -206, -189, -173, -158, -145, -133, -122, -112, -102, -94,
+  -86, -79, -72,
+  -66, -61, -56, -51, -47, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34,
+  -33,
+  -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18,
+  -17,
+  -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1
+};
+
+void
+ipaudio_uncompress (short *buffer, unsigned short buf_len,
+    const unsigned char *data, unsigned char channels)
+{
+  int i, out = 0;
+  int predictor[2];
+  int channel_number = 0;
+
+  for (i = 0; i < channels; ++i) {
+    predictor[i] = GST_READ_UINT16_LE (data);
+    data += 2;
+    if (predictor[i] & 0x8000)
+      predictor[i] -= 0x10000;
+    buffer[out++] = predictor[i];
+  }
+
+  /* we count in 16-bit ints, so adjust the buffer size */
+  buf_len /= 2;
+  while (out < buf_len) {
+    predictor[channel_number] += delta_table[*data++];
+    if (predictor[channel_number] < -32768)
+      predictor[channel_number] = -32768;
+    else if (predictor[channel_number] > 32767)
+      predictor[channel_number] = 32767;
+    buffer[out++] = predictor[channel_number];
+
+    /* toggle channel */
+    channel_number ^= channels - 1;
+  }
+}
diff --git a/gst/mve/mveaudioenc.c b/gst/mve/mveaudioenc.c
new file mode 100644 (file)
index 0000000..1de7375
--- /dev/null
@@ -0,0 +1,120 @@
+/* 
+ * Interplay MVE audio compressor
+ * Copyright (C) 2003, 2004 Alexander Belyakov <abel@krasu.ru>
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <math.h>
+#include <gst/gst.h>
+
+static const gint32 dec_table[256] =
+    { \r0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, \r16, 17, 18, 19,
+  20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, \r32, 33, 34, 35, 36, 37,
+  38, 39, 40, 41, 42, 43, 47, 51, 56, 61, \r66, 72, 79, 86, 94, 102, 112,
+  122, 133, 145, 158, 173, 189, 206, 225, 245, \r267, 292, 318, 348, 379,
+  414, 452, 493, 538, 587, 640, 699, 763, 832, 908, 991, \r1081, 1180, 1288,
+  1405, 1534, 1673, 1826, 1993, 2175, 2373, 2590, 2826, 3084, 3365, 3672,
+  4008, \r4373, 4772, 5208, 5683, 6202, 6767, 7385, 8059, 8794, 9597, 10472,
+  11428, 12471, 13609, 14851, 16206, \r17685, 19298, 21060, 22981, 25078,
+  27367, 29864, 32589, 35563, 38808, 42350, 46214, 50431, 55033, 60055,
+  65535, \r1, -65535, -60055, -55033, -50431, -46214, -42350, -38808, -35563,
+  -32589, -29864, -27367, -25078, -22981, -21060, -19298, \r-17685, -16206,
+  -14851, -13609, -12471, -11428, -10472, -9597, -8794, -8059, -7385, -6767,
+  -6202, -5683, -5208, -4772, \r-4373, -4008, -3672, -3365, -3084, -2826,
+  -2590, -2373, -2175, -1993, -1826, -1673, -1534, -1405, -1288, -1180,
+  \r-1081, -991, -908, -832, -763, -699, -640, -587, -538, -493, -452, -414,
+  -379, -348, -318, -292, \r-267, -245, -225, -206, -189, -173, -158, -145,
+  -133, -122, -112, -102, -94, -86, -79, -72, \r-66, -61, -56, -51, -47, -43,
+  -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, \r-32, -31, -30, -29,
+  -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, \r-16, -15,
+  -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1 \r
+};
+
+\r\r
+/* This value could be non-optimal. Without knowledge of the value\r
+   distribution in the real signal, the actual optimum cannot be evaluated.\r
+   Should be somewhere between 11.458 and 11.542. */ \r
+static const gdouble DPCM_SCALE = 11.5131;
+\rstatic gint8
+mve_enc_delta (guint n)
+{
+  if (n < 44)
+    return n;
+  return floor (DPCM_SCALE * log (n));
+}
+
+gint
+mve_compress_audio (guint8 * dest, const guint8 * src, guint16 len,
+    guint8 channels)
+{
+  gint16 prev[2], s;
+  gint delta, real_res;
+  gint cur_chan;
+  guint8 v;
+
+  for (cur_chan = 0; cur_chan < channels; ++cur_chan) {
+    prev[cur_chan] = GST_READ_UINT16_LE (src);
+    GST_WRITE_UINT16_LE (dest, prev[cur_chan]);
+    src += 2;
+    dest += 2;
+    len -= 2;
+  }
+
+  cur_chan = 0;
+  while (len > 0) {
+    s = GST_READ_UINT16_LE (src);
+    src += 2;
+
+    delta = s - prev[cur_chan];
+    \rif (delta >= 0)
+      \rv = mve_enc_delta (delta);
+    \r
+    else
+      \rv = 256 - mve_enc_delta (-delta);
+    \r\rreal_res = dec_table[v] + prev[cur_chan];
+    \rif (real_res < -32768 || real_res > 32767) {
+      \r
+          /* correct overflow */ \r
+          /* GST_DEBUG ("co:%d + %d = %d -> new v:%d, dec_table:%d will be %d",
+             prev[cur_chan], dec_table[v], real_res,
+             v, dec_table[v], prev[cur_chan]+dec_table[v]); */
+          if (s > 0) {
+        \rif (real_res > 32767)
+          --v;
+      \r} else {
+        \rif (real_res < -32768)
+          ++v;
+      \r}
+
+      real_res = dec_table[v] + prev[cur_chan];
+    \r}
+
+    if (G_UNLIKELY (abs (real_res - s) > 32767)) {
+      GST_ERROR ("sign loss left unfixed in audio stream, deviation:%ld",
+          real_res - s);
+      return -1;
+    }
+    \r\r*dest++ = v;
+    \r--len;
+    /* use previous output instead of input. That way output will not go too far from input. */ \r
+        prev[cur_chan] += dec_table[v];
+    cur_chan = channels - 1 - cur_chan;
+  \r}
+
+  return 0;
+}
diff --git a/gst/mve/mvevideodec16.c b/gst/mve/mvevideodec16.c
new file mode 100644 (file)
index 0000000..8a46a59
--- /dev/null
@@ -0,0 +1,849 @@
+/*
+ * Interplay MVE Video Decoder (16 bit)
+ * Copyright (C) 2003 the ffmpeg project, Mike Melanson
+ *           (C) 2006 Jens Granseuer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * For more information about the Interplay MVE format, visit:
+ *   http://www.pcisys.net/~melanson/codecs/interplay-mve.txt
+ */
+
+#include "gstmvedemux.h"
+#include <string.h>
+
+#define PIXEL(s) GST_READ_UINT16_LE (s)
+
+#define CHECK_STREAM(l, n) \
+  do { \
+    if (G_UNLIKELY (*(l) < (n))) { \
+      GST_ERROR ("wanted to read %d bytes from stream, %d available", (n), *(l)); \
+      return -1; \
+    } \
+    *(l) -= (n); \
+  } while (0)
+
+/* copy an 8x8 block from the stream to the frame buffer */
+static int
+ipvideo_copy_block (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned short *src, int offset)
+{
+  int i;
+  int frame_offset;
+
+  frame_offset = frame - (unsigned short *) s->back_buf1 + offset;
+
+  if (G_UNLIKELY (frame_offset < 0)) {
+    GST_ERROR ("frame offset < 0 (%ld)", frame_offset);
+    return -1;
+  } else if (G_UNLIKELY (frame_offset > s->max_block_offset)) {
+    GST_ERROR ("frame offset above limit (%ld > %ld)",
+        frame_offset, s->max_block_offset);
+    return -1;
+  }
+
+  for (i = 0; i < 8; ++i) {
+    memcpy (frame, src, 16);
+    frame += s->width;
+    src += s->width;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0x2 (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  unsigned char B;
+  int x, y;
+  int offset;
+
+  /* copy block from 2 frames ago using a motion vector */
+  CHECK_STREAM (len, 1);
+  B = *(*data)++;
+
+  if (B < 56) {
+    x = 8 + (B % 7);
+    y = B / 7;
+  } else {
+    x = -14 + ((B - 56) % 29);
+    y = 8 + ((B - 56) / 29);
+  }
+  offset = y * s->width + x;
+
+  return ipvideo_copy_block (s, frame, frame + offset, offset);
+}
+
+static int
+ipvideo_decode_0x3 (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  unsigned char B;
+  int x, y;
+  int offset;
+
+  /* copy 8x8 block from current frame from an up/left block */
+  CHECK_STREAM (len, 1);
+  B = *(*data)++;
+
+  if (B < 56) {
+    x = -(8 + (B % 7));
+    y = -(B / 7);
+  } else {
+    x = -(-14 + ((B - 56) % 29));
+    y = -(8 + ((B - 56) / 29));
+  }
+  offset = y * s->width + x;
+
+  return ipvideo_copy_block (s, frame, frame + offset, offset);
+}
+
+static int
+ipvideo_decode_0x4 (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned char B;
+  int offset;
+
+  /* copy a block from the previous frame */
+  CHECK_STREAM (len, 1);
+  B = *(*data)++;
+  x = -8 + (B & 0x0F);
+  y = -8 + (B >> 4);
+  offset = y * s->width + x;
+
+  return ipvideo_copy_block (s, frame, frame +
+      ((unsigned short *) s->back_buf2 - (unsigned short *) s->back_buf1) +
+      offset, offset);
+}
+
+static int
+ipvideo_decode_0x5 (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  signed char x, y;
+  int offset;
+
+  /* copy a block from the previous frame using an expanded range */
+  CHECK_STREAM (len, 2);
+  x = (signed char) *(*data)++;
+  y = (signed char) *(*data)++;
+  offset = y * s->width + x;
+
+  return ipvideo_copy_block (s, frame, frame +
+      ((unsigned short *) s->back_buf2 - (unsigned short *) s->back_buf1) +
+      offset, offset);
+}
+
+static int
+ipvideo_decode_0x7 (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned short P0, P1;
+  unsigned int flags;
+  int bitmask;
+
+  /* 2-color encoding */
+  CHECK_STREAM (len, 4 + 2);
+  P0 = PIXEL (*data);
+  (*data) += 2;
+  P1 = PIXEL (*data);
+  (*data) += 2;
+
+  if (!(P0 & 0x8000)) {
+
+    /* need 8 more bytes from the stream */
+    CHECK_STREAM (len, 8 - 2);
+
+    for (y = 0; y < 8; ++y) {
+      flags = *(*data)++;
+      for (x = 0x01; x <= 0x80; x <<= 1) {
+        if (flags & x)
+          *frame++ = P1;
+        else
+          *frame++ = P0;
+      }
+      frame += s->width - 8;
+    }
+
+  } else {
+    P0 &= ~0x8000;
+
+    /* need 2 more bytes from the stream */
+
+    flags = ((*data)[1] << 8) | (*data)[0];
+    (*data) += 2;
+    bitmask = 0x0001;
+    for (y = 0; y < 8; y += 2) {
+      for (x = 0; x < 8; x += 2, bitmask <<= 1) {
+        if (flags & bitmask) {
+          *(frame + x) = P1;
+          *(frame + x + 1) = P1;
+          *(frame + s->width + x) = P1;
+          *(frame + s->width + x + 1) = P1;
+        } else {
+          *(frame + x) = P0;
+          *(frame + x + 1) = P0;
+          *(frame + s->width + x) = P0;
+          *(frame + s->width + x + 1) = P0;
+        }
+      }
+      frame += s->width * 2;
+    }
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0x8 (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned short P[8];
+  unsigned char B[8];
+  unsigned int flags = 0;
+  unsigned int bitmask = 0;
+  unsigned short P0 = 0, P1 = 0;
+  int lower_half = 0;
+
+  /* 2-color encoding for each 4x4 quadrant, or 2-color encoding on
+   * either top and bottom or left and right halves */
+  CHECK_STREAM (len, 6 + 10);
+
+  P[0] = PIXEL (*data);
+  (*data) += 2;
+  P[1] = PIXEL (*data);
+  (*data) += 2;
+  B[0] = *(*data)++;
+  B[1] = *(*data)++;
+
+  if (!(P[0] & 0x8000)) {
+
+    /* need 18 more bytes */
+    CHECK_STREAM (len, 18 - 10);
+
+    P[2] = PIXEL (*data);
+    (*data) += 2;
+    P[3] = PIXEL (*data);
+    (*data) += 2;
+    B[2] = *(*data)++;
+    B[3] = *(*data)++;
+    P[4] = PIXEL (*data);
+    (*data) += 2;
+    P[5] = PIXEL (*data);
+    (*data) += 2;
+    B[4] = *(*data)++;
+    B[5] = *(*data)++;
+    P[6] = PIXEL (*data);
+    (*data) += 2;
+    P[7] = PIXEL (*data);
+    (*data) += 2;
+    B[6] = *(*data)++;
+    B[7] = *(*data)++;
+
+    flags =
+        ((B[0] & 0xF0) << 4) | ((B[4] & 0xF0) << 8) |
+        ((B[0] & 0x0F)) | ((B[4] & 0x0F) << 4) |
+        ((B[1] & 0xF0) << 20) | ((B[5] & 0xF0) << 24) |
+        ((B[1] & 0x0F) << 16) | ((B[5] & 0x0F) << 20);
+    bitmask = 0x00000001;
+    lower_half = 0;             /* still on top half */
+
+    for (y = 0; y < 8; ++y) {
+
+      /* time to reload flags? */
+      if (y == 4) {
+        flags =
+            ((B[2] & 0xF0) << 4) | ((B[6] & 0xF0) << 8) |
+            ((B[2] & 0x0F)) | ((B[6] & 0x0F) << 4) |
+            ((B[3] & 0xF0) << 20) | ((B[7] & 0xF0) << 24) |
+            ((B[3] & 0x0F) << 16) | ((B[7] & 0x0F) << 20);
+        bitmask = 0x00000001;
+        lower_half = 2;
+      }
+
+      /* get the pixel values ready for this quadrant */
+      P0 = P[lower_half + 0];
+      P1 = P[lower_half + 1];
+
+      for (x = 0; x < 8; ++x, bitmask <<= 1) {
+        if (x == 4) {
+          P0 = P[lower_half + 4];
+          P1 = P[lower_half + 5];
+        }
+
+        if (flags & bitmask)
+          *frame++ = P1;
+        else
+          *frame++ = P0;
+      }
+      frame += s->width - 8;
+    }
+
+  } else {
+    P[0] &= ~0x8000;
+
+    /* need 10 more bytes */
+    B[2] = *(*data)++;
+    B[3] = *(*data)++;
+    P[2] = PIXEL (*data);
+    (*data) += 2;
+    P[3] = PIXEL (*data);
+    (*data) += 2;
+    B[4] = *(*data)++;
+    B[5] = *(*data)++;
+    B[6] = *(*data)++;
+    B[7] = *(*data)++;
+
+    if (!(P[2] & 0x8000)) {
+      /* vertical split; left & right halves are 2-color encoded */
+
+      flags =
+          ((B[0] & 0xF0) << 4) | ((B[4] & 0xF0) << 8) |
+          ((B[0] & 0x0F)) | ((B[4] & 0x0F) << 4) |
+          ((B[1] & 0xF0) << 20) | ((B[5] & 0xF0) << 24) |
+          ((B[1] & 0x0F) << 16) | ((B[5] & 0x0F) << 20);
+      bitmask = 0x00000001;
+
+      for (y = 0; y < 8; ++y) {
+
+        /* time to reload flags? */
+        if (y == 4) {
+          flags =
+              ((B[2] & 0xF0) << 4) | ((B[6] & 0xF0) << 8) |
+              ((B[2] & 0x0F)) | ((B[6] & 0x0F) << 4) |
+              ((B[3] & 0xF0) << 20) | ((B[7] & 0xF0) << 24) |
+              ((B[3] & 0x0F) << 16) | ((B[7] & 0x0F) << 20);
+          bitmask = 0x00000001;
+        }
+
+        /* get the pixel values ready for this half */
+        P0 = P[0];
+        P1 = P[1];
+
+        for (x = 0; x < 8; ++x, bitmask <<= 1) {
+          if (x == 4) {
+            P0 = P[2];
+            P1 = P[3];
+          }
+
+          if (flags & bitmask)
+            *frame++ = P1;
+          else
+            *frame++ = P0;
+        }
+        frame += s->width - 8;
+      }
+
+    } else {
+      /* horizontal split; top & bottom halves are 2-color encoded */
+
+      P0 = P[0];
+      P1 = P[1];
+
+      for (y = 0; y < 8; ++y) {
+
+        flags = B[y];
+        if (y == 4) {
+          P0 = P[2] & ~0x8000;
+          P1 = P[3];
+        }
+
+        for (bitmask = 0x01; bitmask <= 0x80; bitmask <<= 1) {
+
+          if (flags & bitmask)
+            *frame++ = P1;
+          else
+            *frame++ = P0;
+        }
+        frame += s->width - 8;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0x9 (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned short P[4];
+  unsigned char B[4];
+  unsigned int flags = 0;
+  int shifter = 0;
+  unsigned short pix;
+
+  /* 4-color encoding */
+  CHECK_STREAM (len, 8 + 4);
+
+  P[0] = PIXEL (*data);
+  (*data) += 2;
+  P[1] = PIXEL (*data);
+  (*data) += 2;
+  P[2] = PIXEL (*data);
+  (*data) += 2;
+  P[3] = PIXEL (*data);
+  (*data) += 2;
+
+  if (!(P[0] & 0x8000) && !(P[2] & 0x8000)) {
+
+    /* 1 of 4 colors for each pixel, need 16 more bytes */
+    CHECK_STREAM (len, 16 - 4);
+
+    for (y = 0; y < 8; ++y) {
+      /* get the next set of 8 2-bit flags */
+      flags = ((*data)[1] << 8) | (*data)[0];
+      (*data) += 2;
+      for (x = 0, shifter = 0; x < 8; ++x, shifter += 2) {
+        *frame++ = P[(flags >> shifter) & 0x03];
+      }
+      frame += s->width - 8;
+    }
+
+  } else if (!(P[0] & 0x8000) && (P[2] & 0x8000)) {
+    P[2] &= ~0x8000;
+
+    /* 1 of 4 colors for each 2x2 block, need 4 more bytes */
+
+    B[0] = *(*data)++;
+    B[1] = *(*data)++;
+    B[2] = *(*data)++;
+    B[3] = *(*data)++;
+    flags = (B[3] << 24) | (B[2] << 16) | (B[1] << 8) | B[0];
+    shifter = 0;
+
+    for (y = 0; y < 8; y += 2) {
+      for (x = 0; x < 8; x += 2, shifter += 2) {
+        pix = P[(flags >> shifter) & 0x03];
+        *(frame + x) = pix;
+        *(frame + x + 1) = pix;
+        *(frame + s->width + x) = pix;
+        *(frame + s->width + x + 1) = pix;
+      }
+      frame += s->width * 2;
+    }
+
+  } else if ((P[0] & 0x8000) && !(P[2] & 0x8000)) {
+    P[0] &= ~0x8000;
+
+    /* 1 of 4 colors for each 2x1 block, need 8 more bytes */
+
+    CHECK_STREAM (len, 8 - 4);
+    for (y = 0; y < 8; ++y) {
+      /* time to reload flags? */
+      if ((y == 0) || (y == 4)) {
+        B[0] = *(*data)++;
+        B[1] = *(*data)++;
+        B[2] = *(*data)++;
+        B[3] = *(*data)++;
+        flags = (B[3] << 24) | (B[2] << 16) | (B[1] << 8) | B[0];
+        shifter = 0;
+      }
+      for (x = 0; x < 8; x += 2, shifter += 2) {
+        pix = P[(flags >> shifter) & 0x03];
+        *(frame + x) = pix;
+        *(frame + x + 1) = pix;
+      }
+      frame += s->width;
+    }
+
+  } else {
+    P[0] &= ~0x8000;
+    P[2] &= ~0x8000;
+
+    /* 1 of 4 colors for each 1x2 block, need 8 more bytes */
+    CHECK_STREAM (len, 8 - 4);
+
+    for (y = 0; y < 8; y += 2) {
+      /* time to reload flags? */
+      if ((y == 0) || (y == 4)) {
+        B[0] = *(*data)++;
+        B[1] = *(*data)++;
+        B[2] = *(*data)++;
+        B[3] = *(*data)++;
+        flags = (B[3] << 24) | (B[2] << 16) | (B[1] << 8) | B[0];
+        shifter = 0;
+      }
+      for (x = 0; x < 8; ++x, shifter += 2) {
+        pix = P[(flags >> shifter) & 0x03];
+        *(frame + x) = pix;
+        *(frame + s->width + x) = pix;
+      }
+      frame += s->width * 2;
+    }
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xa (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned short P[16];
+  unsigned char B[16];
+  int flags = 0;
+  int shifter = 0;
+  int index;
+  int split;
+  int lower_half;
+
+  /* 4-color encoding for each 4x4 quadrant, or 4-color encoding on
+   * either top and bottom or left and right halves */
+  CHECK_STREAM (len, 8 + 24);
+
+  P[0] = PIXEL (*data);
+  (*data) += 2;
+  P[1] = PIXEL (*data);
+  (*data) += 2;
+  P[2] = PIXEL (*data);
+  (*data) += 2;
+  P[3] = PIXEL (*data);
+  (*data) += 2;
+
+  if (!(P[0] & 0x8000)) {
+
+    /* 4-color encoding for each quadrant; need 40 more bytes */
+    CHECK_STREAM (len, 40 - 24);
+
+    B[0] = *(*data)++;
+    B[1] = *(*data)++;
+    B[2] = *(*data)++;
+    B[3] = *(*data)++;
+    for (y = 4; y < 16; y += 4) {
+      for (x = y; x < y + 4; ++x) {
+        P[x] = PIXEL (*data);
+        (*data) += 2;
+      }
+      for (x = y; x < y + 4; ++x)
+        B[x] = *(*data)++;
+    }
+
+    for (y = 0; y < 8; ++y) {
+
+      lower_half = (y >= 4) ? 4 : 0;
+      flags = (B[y + 8] << 8) | B[y];
+
+      for (x = 0, shifter = 0; x < 8; ++x, shifter += 2) {
+        split = (x >= 4) ? 8 : 0;
+        index = split + lower_half + ((flags >> shifter) & 0x03);
+        *frame++ = P[index];
+      }
+
+      frame += s->width - 8;
+    }
+
+  } else {
+    P[0] &= ~0x8000;
+
+    /* 4-color encoding for either left and right or top and bottom
+     * halves; need 24 more bytes */
+
+    memcpy (&B[0], *data, 8);
+    (*data) += 8;
+    P[4] = PIXEL (*data);
+    (*data) += 2;
+    P[5] = PIXEL (*data);
+    (*data) += 2;
+    P[6] = PIXEL (*data);
+    (*data) += 2;
+    P[7] = PIXEL (*data);
+    (*data) += 2;
+    memcpy (&B[8], *data, 8);
+    (*data) += 8;
+
+    if (!(P[4] & 0x8000)) {
+
+      /* block is divided into left and right halves */
+      for (y = 0; y < 8; ++y) {
+
+        flags = (B[y + 8] << 8) | B[y];
+        split = 0;
+
+        for (x = 0, shifter = 0; x < 8; ++x, shifter += 2) {
+          if (x == 4)
+            split = 4;
+          *frame++ = P[split + ((flags >> shifter) & 0x03)];
+        }
+
+        frame += s->width - 8;
+      }
+
+    } else {
+      P[4] &= ~0x8000;
+
+      /* block is divided into top and bottom halves */
+      split = 0;
+      for (y = 0; y < 8; ++y) {
+
+        flags = (B[y * 2 + 1] << 8) | B[y * 2];
+        if (y == 4)
+          split = 4;
+
+        for (x = 0, shifter = 0; x < 8; ++x, shifter += 2)
+          *frame++ = P[split + ((flags >> shifter) & 0x03)];
+
+        frame += s->width - 8;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xb (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+
+  /* 64-color encoding (each pixel in block is a different color) */
+  CHECK_STREAM (len, 128);
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; ++x) {
+      *frame++ = PIXEL (*data);
+      (*data) += 2;
+    }
+    frame += s->width - 8;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xc (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned short pix;
+
+  /* 16-color block encoding: each 2x2 block is a different color */
+  CHECK_STREAM (len, 32);
+
+  for (y = 0; y < 8; y += 2) {
+    for (x = 0; x < 8; x += 2) {
+      pix = PIXEL (*data);
+      (*data) += 2;
+      *(frame + x) = pix;
+      *(frame + x + 1) = pix;
+      *(frame + s->width + x) = pix;
+      *(frame + s->width + x + 1) = pix;
+    }
+    frame += s->width * 2;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xd (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned short P[4];
+  unsigned char index = 0;
+
+  /* 4-color block encoding: each 4x4 block is a different color */
+  CHECK_STREAM (len, 8);
+
+  P[0] = PIXEL (*data);
+  (*data) += 2;
+  P[1] = PIXEL (*data);
+  (*data) += 2;
+  P[2] = PIXEL (*data);
+  (*data) += 2;
+  P[3] = PIXEL (*data);
+  (*data) += 2;
+
+  for (y = 0; y < 8; ++y) {
+    if (y < 4)
+      index = 0;
+    else
+      index = 2;
+
+    for (x = 0; x < 8; ++x) {
+      if (x == 4)
+        ++index;
+      *frame++ = P[index];
+    }
+    frame += s->width - 8;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xe (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned short pix;
+
+  /* 1-color encoding: the whole block is 1 solid color */
+  CHECK_STREAM (len, 2);
+
+  pix = PIXEL (*data);
+  (*data) += 2;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; ++x) {
+      *frame++ = pix;
+    }
+    frame += s->width - 8;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xf (const GstMveDemuxStream * s, unsigned short *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned short P[2];
+
+  /* dithered encoding */
+  CHECK_STREAM (len, 4);
+
+  P[0] = PIXEL (*data);
+  (*data) += 2;
+  P[1] = PIXEL (*data);
+  (*data) += 2;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 4; ++x) {
+      *frame++ = P[y & 1];
+      *frame++ = P[(y & 1) ^ 1];
+    }
+    frame += s->width - 8;
+  }
+
+  return 0;
+}
+
+int
+ipvideo_decode_frame16 (const GstMveDemuxStream * s, const unsigned char *data,
+    unsigned short len)
+{
+  int rc = 0;
+  int x, y, xx, yy;
+  int index = 0;
+  unsigned short offset;
+  unsigned char opcode;
+  unsigned short *frame;
+  const unsigned char *data2;
+  unsigned short len2;
+
+  CHECK_STREAM (&len, 2);
+
+  offset = (data[1] << 8) | data[0];
+  data2 = data + offset;
+  len2 = len - offset + 2;
+  data += 2;
+
+  frame = (unsigned short *) s->back_buf1;
+
+  /* decoding is done in 8x8 blocks */
+  xx = s->width >> 3;
+  yy = s->height >> 3;
+
+  for (y = 0; y < yy; ++y) {
+    for (x = 0; x < xx; ++x) {
+      /* decoding map contains 4 bits of information per 8x8 block */
+      /* bottom nibble first, then top nibble */
+      if (index & 1)
+        opcode = s->code_map[index >> 1] >> 4;
+      else
+        opcode = s->code_map[index >> 1] & 0x0F;
+      ++index;
+
+      /* GST_DEBUG ("block @ (%3d, %3d): encoding 0x%X, data ptr @ %p",
+         x, y, opcode, data); */
+
+      switch (opcode) {
+        case 0x0:
+          /* copy a block from the previous frame */
+          rc = ipvideo_copy_block (s, frame, frame +
+              ((unsigned short *) s->back_buf2 -
+                  (unsigned short *) s->back_buf1), 0);
+          break;
+        case 0x1:
+          /* copy block from 2 frames ago; since we switched the back
+           * buffers we don't actually have to do anything here */
+          break;
+        case 0x2:
+          rc = ipvideo_decode_0x2 (s, frame, &data2, &len2);
+          break;
+        case 0x3:
+          rc = ipvideo_decode_0x3 (s, frame, &data2, &len2);
+          break;
+        case 0x4:
+          rc = ipvideo_decode_0x4 (s, frame, &data2, &len2);
+          break;
+        case 0x5:
+          rc = ipvideo_decode_0x5 (s, frame, &data, &len);
+          break;
+        case 0x6:
+          /* mystery opcode? skip multiple blocks? */
+          GST_WARNING ("encountered unsupported opcode 0x6");
+          rc = -1;
+          break;
+        case 0x7:
+          rc = ipvideo_decode_0x7 (s, frame, &data, &len);
+          break;
+        case 0x8:
+          rc = ipvideo_decode_0x8 (s, frame, &data, &len);
+          break;
+        case 0x9:
+          rc = ipvideo_decode_0x9 (s, frame, &data, &len);
+          break;
+        case 0xa:
+          rc = ipvideo_decode_0xa (s, frame, &data, &len);
+          break;
+        case 0xb:
+          rc = ipvideo_decode_0xb (s, frame, &data, &len);
+          break;
+        case 0xc:
+          rc = ipvideo_decode_0xc (s, frame, &data, &len);
+          break;
+        case 0xd:
+          rc = ipvideo_decode_0xd (s, frame, &data, &len);
+          break;
+        case 0xe:
+          rc = ipvideo_decode_0xe (s, frame, &data, &len);
+          break;
+        case 0xf:
+          rc = ipvideo_decode_0xf (s, frame, &data, &len);
+          break;
+      }
+
+      if (rc != 0)
+        return rc;
+
+      frame += 8;
+    }
+    frame += 7 * s->width;
+  }
+
+  return 0;
+}
diff --git a/gst/mve/mvevideodec8.c b/gst/mve/mvevideodec8.c
new file mode 100644 (file)
index 0000000..73e9172
--- /dev/null
@@ -0,0 +1,802 @@
+/*
+ * Interplay MVE Video Decoder (8 bit)
+ * Copyright (C) 2003 the ffmpeg project, Mike Melanson
+ *           (C) 2006 Jens Granseuer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * For more information about the Interplay MVE format, visit:
+ *   http://www.pcisys.net/~melanson/codecs/interplay-mve.txt
+ */
+
+#include "gstmvedemux.h"
+#include <string.h>
+
+#define CHECK_STREAM(l, n) \
+  do { \
+    if (G_UNLIKELY (*(l) < (n))) { \
+      GST_ERROR ("wanted to read %d bytes from stream, %d available", (n), *(l)); \
+      return -1; \
+    } \
+    *(l) -= (n); \
+  } while (0)
+
+
+/* copy an 8x8 block from the stream to the frame buffer */
+static int
+ipvideo_copy_block (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char *src, int offset)
+{
+  int i;
+  long frame_offset;
+
+  frame_offset = frame - s->back_buf1 + offset;
+
+  if (G_UNLIKELY (frame_offset < 0)) {
+    GST_ERROR ("frame offset < 0 (%ld)", frame_offset);
+    return -1;
+  } else if (G_UNLIKELY (frame_offset > s->max_block_offset)) {
+    GST_ERROR ("frame offset above limit (%ld > %ld)",
+        frame_offset, s->max_block_offset);
+    return -1;
+  }
+
+  for (i = 0; i < 8; ++i) {
+    memcpy (frame, src, 8);
+    frame += s->width;
+    src += s->width;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0x2 (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  unsigned char B;
+  int x, y;
+  int offset;
+
+  /* copy block from 2 frames ago using a motion vector */
+  CHECK_STREAM (len, 1);
+  B = *(*data)++;
+
+  if (B < 56) {
+    x = 8 + (B % 7);
+    y = B / 7;
+  } else {
+    x = -14 + ((B - 56) % 29);
+    y = 8 + ((B - 56) / 29);
+  }
+  offset = y * s->width + x;
+
+  return ipvideo_copy_block (s, frame, frame + offset, offset);
+}
+
+static int
+ipvideo_decode_0x3 (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  unsigned char B;
+  int x, y;
+  int offset;
+
+  /* copy 8x8 block from current frame from an up/left block */
+  CHECK_STREAM (len, 1);
+  B = *(*data)++;
+
+  if (B < 56) {
+    x = -(8 + (B % 7));
+    y = -(B / 7);
+  } else {
+    x = -(-14 + ((B - 56) % 29));
+    y = -(8 + ((B - 56) / 29));
+  }
+  offset = y * s->width + x;
+
+  return ipvideo_copy_block (s, frame, frame + offset, offset);
+}
+
+static int
+ipvideo_decode_0x4 (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  unsigned char B;
+  int x, y;
+  int offset;
+
+  /* copy a block from the previous frame */
+  CHECK_STREAM (len, 1);
+  B = *(*data)++;
+  x = -8 + (B & 0x0F);
+  y = -8 + (B >> 4);
+  offset = y * s->width + x;
+
+  return ipvideo_copy_block (s, frame,
+      frame + (s->back_buf2 - s->back_buf1) + offset, offset);
+}
+
+static int
+ipvideo_decode_0x5 (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  signed char x, y;
+  int offset;
+
+  /* copy a block from the previous frame using an expanded range */
+  CHECK_STREAM (len, 2);
+
+  x = (signed char) *(*data)++;
+  y = (signed char) *(*data)++;
+  offset = y * s->width + x;
+
+  return ipvideo_copy_block (s, frame,
+      frame + (s->back_buf2 - s->back_buf1) + offset, offset);
+}
+
+static int
+ipvideo_decode_0x7 (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned char P0, P1;
+  unsigned int flags;
+  int bitmask;
+
+  /* 2-color encoding */
+  CHECK_STREAM (len, 2 + 2);
+
+  P0 = *(*data)++;
+  P1 = *(*data)++;
+
+  if (P0 <= P1) {
+
+    /* need 8 more bytes from the stream */
+    CHECK_STREAM (len, 8 - 2);
+
+    for (y = 0; y < 8; ++y) {
+      flags = *(*data)++;
+      for (x = 0x01; x <= 0x80; x <<= 1) {
+        if (flags & x)
+          *frame++ = P1;
+        else
+          *frame++ = P0;
+      }
+      frame += s->width - 8;
+    }
+
+  } else {
+
+    /* need 2 more bytes from the stream */
+    flags = ((*data)[1] << 8) | (*data)[0];
+    (*data) += 2;
+    bitmask = 0x0001;
+    for (y = 0; y < 8; y += 2) {
+      for (x = 0; x < 8; x += 2, bitmask <<= 1) {
+        if (flags & bitmask) {
+          *(frame + x) = P1;
+          *(frame + x + 1) = P1;
+          *(frame + s->width + x) = P1;
+          *(frame + s->width + x + 1) = P1;
+        } else {
+          *(frame + x) = P0;
+          *(frame + x + 1) = P0;
+          *(frame + s->width + x) = P0;
+          *(frame + s->width + x + 1) = P0;
+        }
+      }
+      frame += s->width * 2;
+    }
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0x8 (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned char P[8];
+  unsigned char B[8];
+  unsigned int flags = 0;
+  unsigned int bitmask = 0;
+  unsigned char P0 = 0, P1 = 0;
+  int lower_half = 0;
+
+  /* 2-color encoding for each 4x4 quadrant, or 2-color encoding on
+   * either top and bottom or left and right halves */
+  CHECK_STREAM (len, 4 + 8);
+
+  P[0] = (*data)[0];
+  P[1] = (*data)[1];
+  B[0] = (*data)[2];
+  B[1] = (*data)[3];
+  (*data) += 4;
+
+  if (P[0] <= P[1]) {
+
+    /* need 12 more bytes */
+    CHECK_STREAM (len, 12 - 8);
+
+    P[2] = (*data)[0];
+    P[3] = (*data)[1];
+    B[2] = (*data)[2];
+    B[3] = (*data)[3];
+    P[4] = (*data)[4];
+    P[5] = (*data)[5];
+    B[4] = (*data)[6];
+    B[5] = (*data)[7];
+    P[6] = (*data)[8];
+    P[7] = (*data)[9];
+    B[6] = (*data)[10];
+    B[7] = (*data)[11];
+    (*data) += 12;
+
+    flags =
+        ((B[0] & 0xF0) << 4) | ((B[4] & 0xF0) << 8) |
+        ((B[0] & 0x0F)) | ((B[4] & 0x0F) << 4) |
+        ((B[1] & 0xF0) << 20) | ((B[5] & 0xF0) << 24) |
+        ((B[1] & 0x0F) << 16) | ((B[5] & 0x0F) << 20);
+    bitmask = 0x00000001;
+    lower_half = 0;             /* still on top half */
+
+    for (y = 0; y < 8; ++y) {
+
+      /* time to reload flags? */
+      if (y == 4) {
+        flags =
+            ((B[2] & 0xF0) << 4) | ((B[6] & 0xF0) << 8) |
+            ((B[2] & 0x0F)) | ((B[6] & 0x0F) << 4) |
+            ((B[3] & 0xF0) << 20) | ((B[7] & 0xF0) << 24) |
+            ((B[3] & 0x0F) << 16) | ((B[7] & 0x0F) << 20);
+        bitmask = 0x00000001;
+        lower_half = 2;
+      }
+
+      /* get the pixel values ready for this quadrant */
+      P0 = P[lower_half + 0];
+      P1 = P[lower_half + 1];
+
+      for (x = 0; x < 8; ++x, bitmask <<= 1) {
+        if (x == 4) {
+          P0 = P[lower_half + 4];
+          P1 = P[lower_half + 5];
+        }
+
+        if (flags & bitmask)
+          *frame++ = P1;
+        else
+          *frame++ = P0;
+      }
+      frame += s->width - 8;
+    }
+
+  } else {
+
+    /* need 8 more bytes */
+    B[2] = (*data)[0];
+    B[3] = (*data)[1];
+    P[2] = (*data)[2];
+    P[3] = (*data)[3];
+    B[4] = (*data)[4];
+    B[5] = (*data)[5];
+    B[6] = (*data)[6];
+    B[7] = (*data)[7];
+    (*data) += 8;
+
+    if (P[2] <= P[3]) {
+
+      /* vertical split; left & right halves are 2-color encoded */
+
+      flags =
+          ((B[0] & 0xF0) << 4) | ((B[4] & 0xF0) << 8) |
+          ((B[0] & 0x0F)) | ((B[4] & 0x0F) << 4) |
+          ((B[1] & 0xF0) << 20) | ((B[5] & 0xF0) << 24) |
+          ((B[1] & 0x0F) << 16) | ((B[5] & 0x0F) << 20);
+      bitmask = 0x00000001;
+
+      for (y = 0; y < 8; ++y) {
+
+        /* time to reload flags? */
+        if (y == 4) {
+          flags =
+              ((B[2] & 0xF0) << 4) | ((B[6] & 0xF0) << 8) |
+              ((B[2] & 0x0F)) | ((B[6] & 0x0F) << 4) |
+              ((B[3] & 0xF0) << 20) | ((B[7] & 0xF0) << 24) |
+              ((B[3] & 0x0F) << 16) | ((B[7] & 0x0F) << 20);
+          bitmask = 0x00000001;
+        }
+
+        /* get the pixel values ready for this half */
+        P0 = P[0];
+        P1 = P[1];
+
+        for (x = 0; x < 8; ++x, bitmask <<= 1) {
+          if (x == 4) {
+            P0 = P[2];
+            P1 = P[3];
+          }
+
+          if (flags & bitmask)
+            *frame++ = P1;
+          else
+            *frame++ = P0;
+        }
+        frame += s->width - 8;
+      }
+
+    } else {
+
+      /* horizontal split; top & bottom halves are 2-color encoded */
+
+      P0 = P[0];
+      P1 = P[1];
+
+      for (y = 0; y < 8; ++y) {
+
+        flags = B[y];
+        if (y == 4) {
+          P0 = P[2];
+          P1 = P[3];
+        }
+
+        for (bitmask = 0x01; bitmask <= 0x80; bitmask <<= 1) {
+
+          if (flags & bitmask)
+            *frame++ = P1;
+          else
+            *frame++ = P0;
+        }
+        frame += s->width - 8;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0x9 (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned char P[4];
+  unsigned char B[4];
+  unsigned long flags = 0;
+  int shifter = 0;
+  unsigned char pix;
+
+  /* 4-color encoding */
+  CHECK_STREAM (len, 4 + 4);
+
+  P[0] = (*data)[0];
+  P[1] = (*data)[1];
+  P[2] = (*data)[2];
+  P[3] = (*data)[3];
+  (*data) += 4;
+
+  if ((P[0] <= P[1]) && (P[2] <= P[3])) {
+
+    /* 1 of 4 colors for each pixel, need 16 more bytes */
+    CHECK_STREAM (len, 16 - 4);
+
+    for (y = 0; y < 8; ++y) {
+      /* get the next set of 8 2-bit flags */
+      flags = ((*data)[1] << 8) | (*data)[0];
+      (*data) += 2;
+      for (x = 0, shifter = 0; x < 8; ++x, shifter += 2) {
+        *frame++ = P[(flags >> shifter) & 0x03];
+      }
+      frame += s->width - 8;
+    }
+
+  } else if ((P[0] <= P[1]) && (P[2] > P[3])) {
+
+    /* 1 of 4 colors for each 2x2 block, need 4 more bytes */
+    B[0] = (*data)[0];
+    B[1] = (*data)[1];
+    B[2] = (*data)[2];
+    B[3] = (*data)[3];
+    (*data) += 4;
+    flags = (B[3] << 24) | (B[2] << 16) | (B[1] << 8) | B[0];
+    shifter = 0;
+
+    for (y = 0; y < 8; y += 2) {
+      for (x = 0; x < 8; x += 2, shifter += 2) {
+        pix = P[(flags >> shifter) & 0x03];
+        *(frame + x) = pix;
+        *(frame + x + 1) = pix;
+        *(frame + s->width + x) = pix;
+        *(frame + s->width + x + 1) = pix;
+      }
+      frame += s->width * 2;
+    }
+
+  } else if ((P[0] > P[1]) && (P[2] <= P[3])) {
+
+    /* 1 of 4 colors for each 2x1 block, need 8 more bytes */
+    CHECK_STREAM (len, 8 - 4);
+
+    for (y = 0; y < 8; ++y) {
+      /* time to reload flags? */
+      if ((y == 0) || (y == 4)) {
+        B[0] = (*data)[0];
+        B[1] = (*data)[1];
+        B[2] = (*data)[2];
+        B[3] = (*data)[3];
+        (*data) += 4;
+        flags = (B[3] << 24) | (B[2] << 16) | (B[1] << 8) | B[0];
+        shifter = 0;
+      }
+      for (x = 0; x < 8; x += 2, shifter += 2) {
+        pix = P[(flags >> shifter) & 0x03];
+        *(frame + x) = pix;
+        *(frame + x + 1) = pix;
+      }
+      frame += s->width;
+    }
+
+  } else {
+
+    /* 1 of 4 colors for each 1x2 block, need 8 more bytes */
+    CHECK_STREAM (len, 8 - 4);
+
+    for (y = 0; y < 8; y += 2) {
+      /* time to reload flags? */
+      if ((y == 0) || (y == 4)) {
+        B[0] = (*data)[0];
+        B[1] = (*data)[1];
+        B[2] = (*data)[2];
+        B[3] = (*data)[3];
+        (*data) += 4;
+        flags = (B[3] << 24) | (B[2] << 16) | (B[1] << 8) | B[0];
+        shifter = 0;
+      }
+      for (x = 0; x < 8; ++x, shifter += 2) {
+        pix = P[(flags >> shifter) & 0x03];
+        *(frame + x) = pix;
+        *(frame + s->width + x) = pix;
+      }
+      frame += s->width * 2;
+    }
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xa (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned char P[16];
+  unsigned char B[16];
+  int flags = 0;
+  int shifter = 0;
+  int index;
+  int split;
+  int lower_half;
+
+  /* 4-color encoding for each 4x4 quadrant, or 4-color encoding on
+   * either top and bottom or left and right halves */
+  CHECK_STREAM (len, 8 + 16);
+
+  P[0] = (*data)[0];
+  P[1] = (*data)[1];
+  P[2] = (*data)[2];
+  P[3] = (*data)[3];
+  B[0] = (*data)[4];
+  B[1] = (*data)[5];
+  B[2] = (*data)[6];
+  B[3] = (*data)[7];
+  (*data) += 8;
+
+  if (P[0] <= P[1]) {
+
+    /* 4-color encoding for each quadrant; need 24 more bytes */
+    CHECK_STREAM (len, 24 - 16);
+
+    for (y = 4; y < 16; y += 4) {
+      for (x = y; x < y + 4; ++x)
+        P[x] = *(*data)++;
+      for (x = y; x < y + 4; ++x)
+        B[x] = *(*data)++;
+    }
+
+    for (y = 0; y < 8; ++y) {
+
+      lower_half = (y >= 4) ? 4 : 0;
+      flags = (B[y + 8] << 8) | B[y];
+
+      for (x = 0, shifter = 0; x < 8; ++x, shifter += 2) {
+        split = (x >= 4) ? 8 : 0;
+        index = split + lower_half + ((flags >> shifter) & 0x03);
+        *frame++ = P[index];
+      }
+
+      frame += s->width - 8;
+    }
+
+  } else {
+
+    /* 4-color encoding for either left and right or top and bottom
+     * halves; need 16 more bytes */
+
+    B[4] = (*data)[0];
+    B[5] = (*data)[1];
+    B[6] = (*data)[2];
+    B[7] = (*data)[3];
+    P[4] = (*data)[4];
+    P[5] = (*data)[5];
+    P[6] = (*data)[6];
+    P[7] = (*data)[7];
+    (*data) += 8;
+    memcpy (&B[8], *data, 8);
+    (*data) += 8;
+
+    if (P[4] <= P[5]) {
+
+      /* block is divided into left and right halves */
+      for (y = 0; y < 8; ++y) {
+
+        flags = (B[y + 8] << 8) | B[y];
+        split = 0;
+
+        for (x = 0, shifter = 0; x < 8; ++x, shifter += 2) {
+          if (x == 4)
+            split = 4;
+          *frame++ = P[split + ((flags >> shifter) & 0x03)];
+        }
+
+        frame += s->width - 8;
+      }
+
+    } else {
+
+      /* block is divided into top and bottom halves */
+      split = 0;
+      for (y = 0; y < 8; ++y) {
+
+        flags = (B[y * 2 + 1] << 8) | B[y * 2];
+        if (y == 4)
+          split = 4;
+
+        for (x = 0, shifter = 0; x < 8; ++x, shifter += 2)
+          *frame++ = P[split + ((flags >> shifter) & 0x03)];
+
+        frame += s->width - 8;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xb (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int y;
+
+  /* 64-color encoding (each pixel in block is a different color) */
+  CHECK_STREAM (len, 64);
+
+  for (y = 0; y < 8; ++y) {
+    memcpy (frame, *data, 8);
+    frame += s->width;
+    (*data) += 8;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xc (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned char pix;
+
+  /* 16-color block encoding: each 2x2 block is a different color */
+  CHECK_STREAM (len, 16);
+
+  for (y = 0; y < 8; y += 2) {
+    for (x = 0; x < 8; x += 2) {
+      pix = *(*data)++;
+      *(frame + x) = pix;
+      *(frame + x + 1) = pix;
+      *(frame + s->width + x) = pix;
+      *(frame + s->width + x + 1) = pix;
+    }
+    frame += s->width * 2;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xd (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned char P[4];
+  unsigned char index = 0;
+
+  /* 4-color block encoding: each 4x4 block is a different color */
+  CHECK_STREAM (len, 4);
+
+  P[0] = (*data)[0];
+  P[1] = (*data)[1];
+  P[2] = (*data)[2];
+  P[3] = (*data)[3];
+  (*data) += 4;
+
+  for (y = 0; y < 8; ++y) {
+    if (y < 4)
+      index = 0;
+    else
+      index = 2;
+
+    for (x = 0; x < 8; ++x) {
+      if (x == 4)
+        ++index;
+      *frame++ = P[index];
+    }
+    frame += s->width - 8;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xe (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int y;
+  unsigned char pix;
+
+  /* 1-color encoding: the whole block is 1 solid color */
+  CHECK_STREAM (len, 1);
+  pix = *(*data)++;
+
+  for (y = 0; y < 8; ++y) {
+    memset (frame, pix, 8);
+    frame += s->width;
+  }
+
+  return 0;
+}
+
+static int
+ipvideo_decode_0xf (const GstMveDemuxStream * s, unsigned char *frame,
+    const unsigned char **data, unsigned short *len)
+{
+  int x, y;
+  unsigned char P[2];
+
+  /* dithered encoding */
+  CHECK_STREAM (len, 2);
+
+  P[0] = *(*data)++;
+  P[1] = *(*data)++;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 4; ++x) {
+      *frame++ = P[y & 1];
+      *frame++ = P[(y & 1) ^ 1];
+    }
+    frame += s->width - 8;
+  }
+
+  return 0;
+}
+
+int
+ipvideo_decode_frame8 (const GstMveDemuxStream * s, const unsigned char *data,
+    unsigned short len)
+{
+  int rc = 0;
+  int x, y, xx, yy;
+  int index = 0;
+  unsigned char opcode;
+  unsigned char *frame;
+
+  frame = s->back_buf1;
+
+  /* decoding is done in 8x8 blocks */
+  xx = s->width >> 3;
+  yy = s->height >> 3;
+
+  for (y = 0; y < yy; ++y) {
+    for (x = 0; x < xx; ++x) {
+      /* decoding map contains 4 bits of information per 8x8 block */
+      /* bottom nibble first, then top nibble */
+      if (index & 1)
+        opcode = s->code_map[index >> 1] >> 4;
+      else
+        opcode = s->code_map[index >> 1] & 0x0F;
+      ++index;
+
+      /* GST_DEBUG ("block @ (%3d, %3d): encoding 0x%X, data ptr @ %p",
+         x, y, opcode, data); */
+
+      switch (opcode) {
+        case 0x00:
+          /* copy a block from the previous frame */
+          rc = ipvideo_copy_block (s, frame,
+              frame + (s->back_buf2 - s->back_buf1), 0);
+          break;
+        case 0x01:
+          /* copy block from 2 frames ago; since we switched the back
+           * buffers we don't actually have to do anything here */
+          break;
+        case 0x02:
+          rc = ipvideo_decode_0x2 (s, frame, &data, &len);
+          break;
+        case 0x03:
+          rc = ipvideo_decode_0x3 (s, frame, &data, &len);
+          break;
+        case 0x04:
+          rc = ipvideo_decode_0x4 (s, frame, &data, &len);
+          break;
+        case 0x05:
+          rc = ipvideo_decode_0x5 (s, frame, &data, &len);
+          break;
+        case 0x06:
+          /* mystery opcode? skip multiple blocks? */
+          GST_WARNING ("encountered unsupported opcode 0x6");
+          rc = -1;
+          break;
+        case 0x07:
+          rc = ipvideo_decode_0x7 (s, frame, &data, &len);
+          break;
+        case 0x08:
+          rc = ipvideo_decode_0x8 (s, frame, &data, &len);
+          break;
+        case 0x09:
+          rc = ipvideo_decode_0x9 (s, frame, &data, &len);
+          break;
+        case 0x0a:
+          rc = ipvideo_decode_0xa (s, frame, &data, &len);
+          break;
+        case 0x0b:
+          rc = ipvideo_decode_0xb (s, frame, &data, &len);
+          break;
+        case 0x0c:
+          rc = ipvideo_decode_0xc (s, frame, &data, &len);
+          break;
+        case 0x0d:
+          rc = ipvideo_decode_0xd (s, frame, &data, &len);
+          break;
+        case 0x0e:
+          rc = ipvideo_decode_0xe (s, frame, &data, &len);
+          break;
+        case 0x0f:
+          rc = ipvideo_decode_0xf (s, frame, &data, &len);
+          break;
+      }
+
+      if (rc != 0)
+        return rc;
+
+      frame += 8;
+    }
+    frame += 7 * s->width;
+  }
+
+  return 0;
+}
diff --git a/gst/mve/mvevideoenc16.c b/gst/mve/mvevideoenc16.c
new file mode 100644 (file)
index 0000000..4a18389
--- /dev/null
@@ -0,0 +1,1649 @@
+/*
+ * Interplay MVE video encoder (16 bit)
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gstmvemux.h"
+
+typedef struct _GstMveEncoderData GstMveEncoderData;
+typedef struct _GstMveEncoding GstMveEncoding;
+typedef struct _GstMveApprox GstMveApprox;
+typedef struct _GstMveQuant GstMveQuant;
+
+#define MVE_RMASK   0x7c00
+#define MVE_GMASK   0x03e0
+#define MVE_BMASK   0x001f
+#define MVE_RSHIFT  10
+#define MVE_GSHIFT  5
+#define MVE_BSHIFT  0
+
+#define MVE_RVAL(p)     (((p) & MVE_RMASK) >> MVE_RSHIFT)
+#define MVE_GVAL(p)     (((p) & MVE_GMASK) >> MVE_GSHIFT)
+#define MVE_BVAL(p)     (((p) & MVE_BMASK) >> MVE_BSHIFT)
+#define MVE_COL(r,g,b)  (((r) << MVE_RSHIFT) | ((g) << MVE_GSHIFT) | ((b) << MVE_BSHIFT))
+
+struct _GstMveEncoderData
+{
+  GstMveMux *mve;
+  /* current position in frame */
+  guint16 x, y;
+
+  /* commonly used quantization results
+     (2 and 4 colors) for the current block */
+  guint16 q2block[64];
+  guint16 q2colors[2];
+  guint32 q2error;
+  gboolean q2available;
+
+  guint16 q4block[64];
+  guint16 q4colors[4];
+  guint32 q4error;
+  gboolean q4available;
+};
+
+struct _GstMveEncoding
+{
+  guint8 opcode;
+  guint8 size;
+    guint32 (*approx) (GstMveEncoderData * enc, const guint16 * src,
+      GstMveApprox * res);
+};
+
+#define MVE_APPROX_MAX_ERROR  G_MAXUINT32
+
+struct _GstMveApprox
+{
+  guint32 error;
+  guint8 type;
+  guint8 data[128];             /* max 128 bytes encoded per block */
+  guint16 block[64];            /* block in final image */
+};
+
+struct _GstMveQuant
+{
+  guint16 col;
+  guint16 r_total, g_total, b_total;
+  guint8 r, g, b;
+  guint8 hits, hits_last;
+  guint32 max_error;
+  guint16 max_miss;
+};
+
+#define mve_median(mve, src) mve_median_sub ((mve), (src), 8, 8, 0)
+#define mve_color_dist(c1, c2) \
+        mve_color_dist_rgb (MVE_RVAL (c1), MVE_GVAL (c1), MVE_BVAL (c1), \
+                            MVE_RVAL (c2), MVE_GVAL (c2), MVE_BVAL (c2));
+
+/* comparison function for qsort() */
+static int
+mve_comp_solution (const void *a, const void *b)
+{
+  const GArray *aa = *((GArray **) a);
+  const GArray *bb = *((GArray **) b);
+
+  if (aa->len <= 1)
+    return G_MAXINT;
+  else if (bb->len <= 1)
+    return G_MININT;
+  else
+    return g_array_index (aa, GstMveApprox, aa->len - 2).error -
+        g_array_index (bb, GstMveApprox, bb->len - 2).error;
+}
+
+static inline guint32
+mve_color_dist_rgb (guint8 r1, guint8 g1, guint8 b1,
+    guint8 r2, guint8 g2, guint8 b2)
+{
+  /* euclidean distance (minus sqrt) */
+  gint dr = r1 - r2;
+  gint dg = g1 - g2;
+  gint db = b1 - b2;
+
+  return dr * dr + dg * dg + db * db;
+}
+
+/* compute average color in a sub-block */
+static guint16
+mve_median_sub (const GstMveMux * mve, const guint16 * src, guint w, guint h,
+    guint n)
+{
+  guint x, y;
+  const guint max = w * h, max2 = max >> 1;
+  guint32 r_total = max2, g_total = max2, b_total = max2;
+
+  src += ((n * w) % 8) + (((n * (8 - h)) / (12 - w)) * h * mve->width);
+
+  for (y = 0; y < h; ++y) {
+    for (x = 0; x < w; ++x) {
+      r_total += MVE_RVAL (src[x]);
+      g_total += MVE_GVAL (src[x]);
+      b_total += MVE_BVAL (src[x]);
+    }
+    src += mve->width;
+  }
+
+  return MVE_COL (r_total / max, g_total / max, b_total / max);
+}
+
+static void
+mve_quant_init (const GstMveMux * mve, GstMveQuant * q, guint n_clusters,
+    const guint16 * data, guint w, guint h)
+{
+  guint i;
+  guint x, y;
+  guint16 cols[4];
+  guint16 val[2];
+
+  /* init first cluster with lowest (darkest), second with highest (lightest)
+     color. if we need 4 clusters, fill in first and last color in the block
+     and hope they make for a good distribution */
+  cols[0] = cols[1] = cols[2] = data[0];
+  cols[3] = data[(h - 1) * mve->width + w - 1];
+
+  /* favour red over green and blue */
+  val[0] = val[1] =
+      (MVE_RVAL (data[0]) << 1) + MVE_GVAL (data[0]) + MVE_BVAL (data[0]);
+
+  for (y = 0; y < h; ++y) {
+    for (x = 0; x < w; ++x) {
+      guint16 c = data[x];
+
+      if ((c != cols[0]) && (c != cols[1])) {
+        guint v = (MVE_RVAL (c) << 1) + MVE_GVAL (c) + MVE_BVAL (c);
+
+        if (v < val[0]) {
+          val[0] = v;
+          cols[0] = c;
+        } else if (v > val[1]) {
+          val[1] = v;
+          cols[1] = c;
+        }
+      }
+    }
+    data += mve->width;
+  }
+
+  for (i = 0; i < n_clusters; ++i) {
+    q[i].col = cols[i];
+    q[i].r = MVE_RVAL (cols[i]);
+    q[i].g = MVE_GVAL (cols[i]);
+    q[i].b = MVE_BVAL (cols[i]);
+    q[i].r_total = q[i].g_total = q[i].b_total = 0;
+    q[i].hits = q[i].hits_last = 0;
+    q[i].max_error = 0;
+    q[i].max_miss = 0;
+  }
+}
+
+static gboolean
+mve_quant_update_clusters (GstMveQuant * q, guint n_clusters)
+{
+  gboolean changed = FALSE;
+  guint i;
+
+  for (i = 0; i < n_clusters; ++i) {
+    if (q[i].hits > 0) {
+      guint16 means = MVE_COL ((q[i].r_total + q[i].hits / 2) / q[i].hits,
+          (q[i].g_total + q[i].hits / 2) / q[i].hits,
+          (q[i].b_total + q[i].hits / 2) / q[i].hits);
+
+      if ((means != q[i].col) || (q[i].hits != q[i].hits_last))
+        changed = TRUE;
+
+      q[i].col = means;
+      q[i].r_total = q[i].g_total = q[i].b_total = 0;
+    } else {
+      guint j;
+      guint32 max_err = 0;
+      GstMveQuant *worst = NULL;
+
+      /* try to replace unused cluster with a better representative */
+      for (j = 0; j < n_clusters; ++j) {
+        if (q[j].max_error > max_err) {
+          worst = &q[j];
+          max_err = worst->max_error;
+        }
+      }
+      if (worst) {
+        q[i].col = worst->max_miss;
+        worst->max_error = 0;
+        changed = TRUE;
+      }
+    }
+
+    q[i].r = MVE_RVAL (q[i].col);
+    q[i].g = MVE_GVAL (q[i].col);
+    q[i].b = MVE_BVAL (q[i].col);
+    q[i].hits_last = q[i].hits;
+    q[i].hits = 0;
+  }
+  for (i = 0; i < n_clusters; ++i) {
+    q[i].max_error = 0;
+  }
+
+  return changed;
+}
+
+/* quantize a sub-block using a k-means algorithm */
+static guint32
+mve_quantize (const GstMveMux * mve, const guint16 * src,
+    guint w, guint h, guint n, guint ncols, guint16 * scratch, guint16 * cols)
+{
+  guint x, y, i;
+  GstMveQuant q[4];
+  const guint16 *data;
+  guint16 *dest;
+  guint32 error;
+
+  g_assert (n <= 4 && ncols <= 4);
+
+  src += ((n * w) % 8) + (((n * (8 - h)) / (12 - w)) * h * mve->width);
+  scratch += ((n * w) % 8) + (((n * (8 - h)) / (12 - w)) * h * 8);
+
+  mve_quant_init (mve, q, ncols, src, w, h);
+
+  do {
+    data = src;
+    dest = scratch;
+    error = 0;
+
+    /* for each pixel find the closest cluster */
+    for (y = 0; y < h; ++y) {
+      for (x = 0; x < w; ++x) {
+        guint16 c = data[x];
+        guint8 r = MVE_RVAL (c), g = MVE_GVAL (c), b = MVE_BVAL (c);
+        guint32 minerr = MVE_APPROX_MAX_ERROR, err;
+        GstMveQuant *best = NULL;
+
+        for (i = 0; i < ncols; ++i) {
+          err = mve_color_dist_rgb (r, g, b, q[i].r, q[i].g, q[i].b);
+
+          if (err < minerr) {
+            minerr = err;
+            best = &q[i];
+          }
+        }
+
+        ++best->hits;
+        best->r_total += r;
+        best->g_total += g;
+        best->b_total += b;
+
+        if (minerr > best->max_error) {
+          best->max_error = minerr;
+          best->max_miss = c;
+        }
+
+        error += minerr;
+        dest[x] = best->col;
+      }
+      data += mve->width;
+      dest += 8;
+    }
+  } while (mve_quant_update_clusters (q, ncols));
+
+  /* fill cols array with result colors */
+  for (i = 0; i < ncols; ++i)
+    cols[i] = q[i].col;
+
+  return error;
+}
+
+static guint32
+mve_block_error (const GstMveMux * mve, const guint16 * b1, const guint16 * b2,
+    guint32 threshold)
+{
+  /* compute error between two blocks in a frame */
+  guint32 e = 0;
+  guint x, y;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; ++x) {
+      e += mve_color_dist (*b1, *b2);
+
+      /* using a threshold to return early gives a huge performance bonus */
+      if (e >= threshold)
+        return MVE_APPROX_MAX_ERROR;
+      ++b1;
+      ++b2;
+    }
+
+    b1 += mve->width - 8;
+    b2 += mve->width - 8;
+  }
+
+  return e;
+}
+
+static guint32
+mve_block_error_packed (const GstMveMux * mve, const guint16 * block,
+    const guint16 * scratch)
+{
+  /* compute error between a block in a frame and a (continuous) scratch pad */
+  guint32 e = 0;
+  guint x, y;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; ++x) {
+      e += mve_color_dist (block[x], scratch[x]);
+    }
+    block += mve->width;
+    scratch += 8;
+  }
+
+  return e;
+}
+
+static void
+mve_store_block (const GstMveMux * mve, const guint16 * block,
+    guint16 * scratch)
+{
+  /* copy block from frame to a (continuous) scratch pad */
+  guint y;
+
+  for (y = 0; y < 8; ++y) {
+    memcpy (scratch, block, 16);
+    block += mve->width;
+    scratch += 8;
+  }
+}
+
+static void
+mve_restore_block (const GstMveMux * mve, guint16 * block,
+    const guint16 * scratch)
+{
+  /* copy block from scratch pad to frame */
+  guint y;
+
+  for (y = 0; y < 8; ++y) {
+    memcpy (block, scratch, 16);
+    block += mve->width;
+    scratch += 8;
+  }
+}
+
+
+static guint32
+mve_try_vector (GstMveEncoderData * enc, const guint16 * src,
+    const guint16 * frame, gint pn, GstMveApprox * apx)
+{
+  /* try to locate a similar 8x8 block in the given frame using a motion vector */
+  guint i;
+  gint dx, dy;
+  gint fx, fy;
+  guint32 err;
+
+  apx->error = MVE_APPROX_MAX_ERROR;
+
+  for (i = 0; i < 256; ++i) {
+    if (i < 56) {
+      dx = 8 + (i % 7);
+      dy = i / 7;
+    } else {
+      dx = -14 + ((i - 56) % 29);
+      dy = 8 + ((i - 56) / 29);
+    }
+
+    fx = enc->x + dx * pn;
+    fy = enc->y + dy * pn;
+
+    if ((fx >= 0) && (fy >= 0) && (fx + 8 <= enc->mve->width)
+        && (fy + 8 <= enc->mve->height)) {
+      err =
+          mve_block_error (enc->mve, src, frame + fy * enc->mve->width + fx,
+          apx->error);
+      if (err < apx->error) {
+        apx->data[0] = i;
+        mve_store_block (enc->mve, frame + fy * enc->mve->width + fx,
+            apx->block);
+        apx->error = err;
+        if (err == 0)
+          return 0;
+      }
+    }
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x0 (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* copy a block from the last frame (0 bytes) */
+  if (enc->mve->last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  mve_store_block (enc->mve,
+      ((guint16 *) GST_BUFFER_DATA (enc->mve->last_frame)) +
+      enc->y * enc->mve->width + enc->x, apx->block);
+  apx->error = mve_block_error_packed (enc->mve, src, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x1 (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* copy a block from the second to last frame (0 bytes) */
+  if (enc->mve->second_last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  mve_store_block (enc->mve,
+      ((guint16 *) GST_BUFFER_DATA (enc->mve->second_last_frame)) +
+      enc->y * enc->mve->width + enc->x, apx->block);
+  apx->error = mve_block_error_packed (enc->mve, src, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x2 (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* copy block from 2 frames ago using a motion vector (1 byte) */
+  if (enc->mve->quick_encoding || enc->mve->second_last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  apx->error = mve_try_vector (enc, src,
+      (guint16 *) GST_BUFFER_DATA (enc->mve->second_last_frame), 1, apx);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x3 (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* copy 8x8 block from current frame from an up/left block (1 byte) */
+  if (enc->mve->quick_encoding)
+    return MVE_APPROX_MAX_ERROR;
+
+  apx->error = mve_try_vector (enc, src,
+      src - enc->mve->width * enc->y - enc->x, -1, apx);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x4 (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* copy a block from previous frame using a motion vector (-8/-8 to +7/+7) (1 byte) */
+  const GstMveMux *mve = enc->mve;
+  guint32 err;
+  const guint16 *frame;
+  gint x1, x2, xi, y1, y2, yi;
+
+  if (mve->last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  frame = (guint16 *) GST_BUFFER_DATA (mve->last_frame);
+
+  x1 = enc->x - 8;
+  x2 = enc->x + 7;
+  if (x1 < 0)
+    x1 = 0;
+  else if (x2 + 8 > mve->width)
+    x2 = mve->width - 8;
+
+  y1 = enc->y - 8;
+  y2 = enc->y + 7;
+  if (y1 < 0)
+    y1 = 0;
+  else if (y2 + 8 > mve->height)
+    y2 = mve->height - 8;
+
+  apx->error = MVE_APPROX_MAX_ERROR;
+
+  for (yi = y1; yi <= y2; ++yi) {
+    guint yoff = yi * mve->width;
+
+    for (xi = x1; xi <= x2; ++xi) {
+      err = mve_block_error (mve, src, frame + yoff + xi, apx->error);
+      if (err < apx->error) {
+        apx->data[0] = ((xi - enc->x + 8) & 0xF) | ((yi - enc->y + 8) << 4);
+        mve_store_block (mve, frame + yoff + xi, apx->block);
+        apx->error = err;
+        if (err == 0)
+          return 0;
+      }
+    }
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x5 (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* copy a block from previous frame using a motion vector
+     (-128/-128 to +127/+127) (2 bytes) */
+  const GstMveMux *mve = enc->mve;
+  guint32 err;
+  const guint16 *frame;
+  gint x1, x2, xi, y1, y2, yi;
+
+  if (mve->quick_encoding || mve->last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  frame = (guint16 *) GST_BUFFER_DATA (mve->last_frame);
+
+  x1 = enc->x - 128;
+  x2 = enc->x + 127;
+  if (x1 < 0)
+    x1 = 0;
+  if (x2 + 8 > mve->width)
+    x2 = mve->width - 8;
+
+  y1 = enc->y - 128;
+  y2 = enc->y + 127;
+  if (y1 < 0)
+    y1 = 0;
+  if (y2 + 8 > mve->height)
+    y2 = mve->height - 8;
+
+  apx->error = MVE_APPROX_MAX_ERROR;
+
+  for (yi = y1; yi <= y2; ++yi) {
+    gint yoff = yi * mve->width;
+
+    for (xi = x1; xi <= x2; ++xi) {
+      err = mve_block_error (mve, src, frame + yoff + xi, apx->error);
+      if (err < apx->error) {
+        apx->data[0] = xi - enc->x;
+        apx->data[1] = yi - enc->y;
+        mve_store_block (mve, frame + yoff + xi, apx->block);
+        apx->error = err;
+        if (err == 0)
+          return 0;
+      }
+    }
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x7a (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 2-color encoding for 2x2 solid blocks (6 bytes) */
+  guint16 p[2];
+  guint16 mean;
+  guint32 e1, e2;
+  guint x, y;
+  guint8 r[2], g[2], b[2], rb, gb, bb;
+  guint16 *block = apx->block;
+  guint16 mask = 0x0001;
+  guint16 flags = 0;
+
+  /* calculate mean colors for the entire block */
+  if (!enc->q2available) {
+    enc->q2error =
+        mve_quantize (enc->mve, src, 8, 8, 0, 2, enc->q2block, enc->q2colors);
+    enc->q2available = TRUE;
+  }
+
+  /* p[0] & 0x8000 */
+  GST_WRITE_UINT16_LE (&apx->data[0], enc->q2colors[0] | 0x8000);
+  GST_WRITE_UINT16_LE (&apx->data[2], enc->q2colors[1]);
+
+  for (x = 0; x < 2; ++x) {
+    r[x] = MVE_RVAL (enc->q2colors[x]);
+    g[x] = MVE_GVAL (enc->q2colors[x]);
+    b[x] = MVE_BVAL (enc->q2colors[x]);
+  }
+
+  /* calculate mean colors for each 2x2 block and map to global colors */
+  for (y = 0; y < 4; ++y) {
+    for (x = 0; x < 4; ++x, mask <<= 1) {
+      p[0] = src[enc->mve->width];
+      p[1] = src[enc->mve->width + 1];
+
+      rb = (MVE_RVAL (src[0]) + MVE_RVAL (src[1]) + MVE_RVAL (p[0]) +
+          MVE_RVAL (p[1]) + 2) / 4;
+      gb = (MVE_GVAL (src[0]) + MVE_GVAL (src[1]) + MVE_GVAL (p[0]) +
+          MVE_GVAL (p[1]) + 2) / 4;
+      bb = (MVE_BVAL (src[0]) + MVE_BVAL (src[1]) + MVE_BVAL (p[0]) +
+          MVE_BVAL (p[1]) + 2) / 4;
+
+      e1 = mve_color_dist_rgb (rb, gb, bb, r[0], g[0], b[0]);
+      e2 = mve_color_dist_rgb (rb, gb, bb, r[1], g[1], b[1]);
+
+      if (e1 > e2) {
+        mean = enc->q2colors[1];
+        flags |= mask;
+      } else {
+        mean = enc->q2colors[0];
+      }
+
+      block[0] = block[1] = block[8] = block[9] = mean;
+
+      src += 2;
+      block += 2;
+    }
+    src += (enc->mve->width * 2) - 8;
+    block += 8;
+  }
+
+  apx->data[4] = flags & 0x00FF;
+  apx->data[5] = (flags & 0xFF00) >> 8;
+
+  apx->error =
+      mve_block_error_packed (enc->mve, src - enc->mve->width * 8, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x7b (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* generic 2-color encoding (12 bytes) */
+  guint x, y;
+  guint8 *data = apx->data;
+  guint16 *block = apx->block;
+
+  if (!enc->q2available) {
+    enc->q2error =
+        mve_quantize (enc->mve, src, 8, 8, 0, 2, enc->q2block, enc->q2colors);
+    enc->q2available = TRUE;
+  }
+
+  memcpy (block, enc->q2block, 128);
+
+  /* !(p[0] & 0x8000) */
+  GST_WRITE_UINT16_LE (&data[0], enc->q2colors[0] & ~0x8000);
+  GST_WRITE_UINT16_LE (&data[2], enc->q2colors[1]);
+  data += 4;
+
+  for (y = 0; y < 8; ++y) {
+    guint8 flags = 0;
+
+    for (x = 0x01; x <= 0x80; x <<= 1) {
+      if (*block == enc->q2colors[1])
+        flags |= x;
+      ++block;
+    }
+    *data++ = flags;
+  }
+
+  apx->error = enc->q2error;
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x8a (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 2-color encoding for top and bottom half (16 bytes) */
+  guint16 cols[2];
+  guint32 flags;
+  guint i, x, y, shifter;
+  guint16 *block = apx->block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 2; ++i) {
+    apx->error += mve_quantize (enc->mve, src, 8, 4, i, 2, apx->block, cols);
+
+    flags = 0;
+    shifter = 0;
+
+    /* p0 & 0x8000 && p2 & 0x8000 */
+    GST_WRITE_UINT16_LE (&data[0], cols[0] | 0x8000);
+    GST_WRITE_UINT16_LE (&data[2], cols[1]);
+
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 8; ++x, ++shifter) {
+        if (block[x] == cols[1])
+          flags |= 1 << shifter;
+      }
+      block += 8;
+    }
+    data[4] = flags & 0x000000FF;
+    data[5] = (flags & 0x0000FF00) >> 8;
+    data[6] = (flags & 0x00FF0000) >> 16;
+    data[7] = (flags & 0xFF000000) >> 24;
+    data += 8;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x8b (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 2-color encoding for left and right half (16 bytes) */
+  guint16 cols[2];
+  guint32 flags;
+  guint i, x, y, shifter;
+  guint16 *block = apx->block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 2; ++i) {
+    apx->error += mve_quantize (enc->mve, src, 4, 8, i, 2, apx->block, cols);
+
+    flags = 0;
+    shifter = 0;
+
+    /* p0 & 0x8000 && !(p2 & 0x8000) */
+    GST_WRITE_UINT16_LE (&data[0], (cols[0] & ~0x8000) | (0x8000 * (i ^ 1)));
+    GST_WRITE_UINT16_LE (&data[2], cols[1]);
+
+    for (y = 0; y < 8; ++y) {
+      for (x = 0; x < 4; ++x, ++shifter) {
+        if (block[x] == cols[1])
+          flags |= 1 << shifter;
+      }
+      block += 8;
+    }
+
+    data[4] = flags & 0x000000FF;
+    data[5] = (flags & 0x0000FF00) >> 8;
+    data[6] = (flags & 0x00FF0000) >> 16;
+    data[7] = (flags & 0xFF000000) >> 24;
+    data += 8;
+    block = apx->block + 4;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x8c (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 2-color encoding for each 4x4 quadrant (24 bytes) */
+  guint16 cols[2];
+  guint16 flags;
+  guint i, x, y, shifter;
+  guint16 *block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 4; ++i) {
+    apx->error +=
+        mve_quantize (enc->mve, src, 4, 4, ((i & 1) << 1) | ((i & 2) >> 1), 2,
+        apx->block, cols);
+
+    /* !(p0 & 0x8000) */
+    GST_WRITE_UINT16_LE (&data[0], cols[0] & ~0x8000);
+    GST_WRITE_UINT16_LE (&data[2], cols[1]);
+
+    block = apx->block + ((i / 2) * 4) + ((i % 2) * 32);
+    flags = 0;
+    shifter = 0;
+
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 4; ++x, ++shifter) {
+        if (block[x] == cols[1])
+          flags |= 1 << shifter;
+      }
+      block += 8;
+    }
+
+    data[4] = flags & 0x00FF;
+    data[5] = (flags & 0xFF00) >> 8;
+    data += 6;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x9a (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for 2x2 solid blocks (12 bytes) */
+  guint16 p[2];
+  guint32 e, emin;
+  guint i, x, y, mean = 0;
+  guint8 r[4], g[4], b[4], rb, gb, bb;
+  guint16 *block = apx->block;
+  guint shifter = 0;
+  guint32 flags = 0;
+
+  /* calculate mean colors for the entire block */
+  if (!enc->q4available) {
+    enc->q4error =
+        mve_quantize (enc->mve, src, 8, 8, 0, 4, enc->q4block, enc->q4colors);
+    enc->q4available = TRUE;
+  }
+
+  /* !(p[0] & 0x8000) && p[2] & 0x8000 */
+  GST_WRITE_UINT16_LE (&apx->data[0], enc->q4colors[0] & ~0x8000);
+  GST_WRITE_UINT16_LE (&apx->data[2], enc->q4colors[1]);
+  GST_WRITE_UINT16_LE (&apx->data[4], enc->q4colors[2] | 0x8000);
+  GST_WRITE_UINT16_LE (&apx->data[6], enc->q4colors[3]);
+
+  for (i = 0; i < 4; ++i) {
+    r[i] = MVE_RVAL (enc->q4colors[i]);
+    g[i] = MVE_GVAL (enc->q4colors[i]);
+    b[i] = MVE_BVAL (enc->q4colors[i]);
+  }
+
+  /* calculate mean colors for each 2x2 block and map to global colors */
+  for (y = 0; y < 4; ++y) {
+    for (x = 0; x < 4; ++x, shifter += 2) {
+      p[0] = src[enc->mve->width];
+      p[1] = src[enc->mve->width + 1];
+
+      rb = (MVE_RVAL (src[0]) + MVE_RVAL (src[1]) + MVE_RVAL (p[0]) +
+          MVE_RVAL (p[1]) + 2) / 4;
+      gb = (MVE_GVAL (src[0]) + MVE_GVAL (src[1]) + MVE_GVAL (p[0]) +
+          MVE_GVAL (p[1]) + 2) / 4;
+      bb = (MVE_BVAL (src[0]) + MVE_BVAL (src[1]) + MVE_BVAL (p[0]) +
+          MVE_BVAL (p[1]) + 2) / 4;
+
+      emin = MVE_APPROX_MAX_ERROR;
+      for (i = 0; i < 4; ++i) {
+        e = mve_color_dist_rgb (rb, gb, bb, r[i], g[i], b[i]);
+        if (e < emin) {
+          emin = e;
+          mean = i;
+        }
+      }
+
+      flags |= mean << shifter;
+      block[0] = block[1] = block[8] = block[9] = enc->q4colors[mean];
+
+      src += 2;
+      block += 2;
+    }
+    src += (enc->mve->width * 2) - 8;
+    block += 8;
+  }
+
+  apx->data[8] = flags & 0x000000FF;
+  apx->data[9] = (flags & 0x0000FF00) >> 8;
+  apx->data[10] = (flags & 0x00FF0000) >> 16;
+  apx->data[11] = (flags & 0xFF000000) >> 24;
+
+  apx->error =
+      mve_block_error_packed (enc->mve, src - 8 * enc->mve->width, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x9b (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for 2x1 solid blocks (16 bytes) */
+  guint32 e, emin;
+  guint i, x, y, mean = 0;
+  guint8 r[4], g[4], b[4], rb, gb, bb;
+  guint8 *data = apx->data;
+  guint16 *block = apx->block;
+  guint shifter = 0;
+  guint32 flags = 0;
+
+  /* calculate mean colors for the entire block */
+  if (!enc->q4available) {
+    enc->q4error =
+        mve_quantize (enc->mve, src, 8, 8, 0, 4, enc->q4block, enc->q4colors);
+    enc->q4available = TRUE;
+  }
+
+  /* p[0] & 0x8000 && !(p[2] & 0x8000) */
+  GST_WRITE_UINT16_LE (&data[0], enc->q4colors[0] | 0x8000);
+  GST_WRITE_UINT16_LE (&data[2], enc->q4colors[1]);
+  GST_WRITE_UINT16_LE (&data[4], enc->q4colors[2] & ~0x8000);
+  GST_WRITE_UINT16_LE (&data[6], enc->q4colors[3]);
+  data += 8;
+
+  for (i = 0; i < 4; ++i) {
+    r[i] = MVE_RVAL (enc->q4colors[i]);
+    g[i] = MVE_GVAL (enc->q4colors[i]);
+    b[i] = MVE_BVAL (enc->q4colors[i]);
+  }
+
+  /* calculate mean colors for each 2x1 block and map to global colors */
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 4; ++x, shifter += 2) {
+      rb = (MVE_RVAL (src[0]) + MVE_RVAL (src[1]) + 1) / 2;
+      gb = (MVE_GVAL (src[0]) + MVE_GVAL (src[1]) + 1) / 2;
+      bb = (MVE_BVAL (src[0]) + MVE_BVAL (src[1]) + 1) / 2;
+
+      emin = MVE_APPROX_MAX_ERROR;
+      for (i = 0; i < 4; ++i) {
+        e = mve_color_dist_rgb (rb, gb, bb, r[i], g[i], b[i]);
+        if (e < emin) {
+          emin = e;
+          mean = i;
+        }
+      }
+
+      flags |= mean << shifter;
+      block[0] = block[1] = enc->q4colors[mean];
+
+      src += 2;
+      block += 2;
+    }
+
+    if ((y == 3) || (y == 7)) {
+      data[0] = flags & 0x000000FF;
+      data[1] = (flags & 0x0000FF00) >> 8;
+      data[2] = (flags & 0x00FF0000) >> 16;
+      data[3] = (flags & 0xFF000000) >> 24;
+      data += 4;
+
+      flags = 0;
+      shifter = 0;
+    }
+
+    src += enc->mve->width - 8;
+  }
+
+  apx->error =
+      mve_block_error_packed (enc->mve, src - 8 * enc->mve->width, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x9c (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for 1x2 solid blocks (16 bytes) */
+  guint16 p2;
+  guint32 e, emin;
+  guint i, x, y, mean = 0;
+  guint8 r[4], g[4], b[4], rb, gb, bb;
+  guint8 *data = apx->data;
+  guint16 *block = apx->block;
+  guint shifter = 0;
+  guint32 flags = 0;
+
+  /* calculate mean colors for the entire block */
+  if (!enc->q4available) {
+    enc->q4error =
+        mve_quantize (enc->mve, src, 8, 8, 0, 4, enc->q4block, enc->q4colors);
+    enc->q4available = TRUE;
+  }
+
+  /* p[0] & 0x8000 && p[2] & 0x8000 */
+  GST_WRITE_UINT16_LE (&data[0], enc->q4colors[0] | 0x8000);
+  GST_WRITE_UINT16_LE (&data[2], enc->q4colors[1]);
+  GST_WRITE_UINT16_LE (&data[4], enc->q4colors[2] | 0x8000);
+  GST_WRITE_UINT16_LE (&data[6], enc->q4colors[3]);
+  data += 8;
+
+  for (i = 0; i < 4; ++i) {
+    r[i] = MVE_RVAL (enc->q4colors[i]);
+    g[i] = MVE_GVAL (enc->q4colors[i]);
+    b[i] = MVE_BVAL (enc->q4colors[i]);
+  }
+
+  /* calculate mean colors for each 1x2 block and map to global colors */
+  for (y = 0; y < 4; ++y) {
+    for (x = 0; x < 8; ++x, shifter += 2) {
+      p2 = src[enc->mve->width];
+      rb = (MVE_RVAL (src[0]) + MVE_RVAL (p2) + 1) / 2;
+      gb = (MVE_GVAL (src[0]) + MVE_GVAL (p2) + 1) / 2;
+      bb = (MVE_BVAL (src[0]) + MVE_BVAL (p2) + 1) / 2;
+
+      emin = MVE_APPROX_MAX_ERROR;
+      for (i = 0; i < 4; ++i) {
+        e = mve_color_dist_rgb (rb, gb, bb, r[i], g[i], b[i]);
+        if (e < emin) {
+          emin = e;
+          mean = i;
+        }
+      }
+
+      flags |= mean << shifter;
+      block[0] = block[8] = enc->q4colors[mean];
+
+      ++src;
+      ++block;
+    }
+
+    if ((y == 1) || (y == 3)) {
+      data[0] = flags & 0x000000FF;
+      data[1] = (flags & 0x0000FF00) >> 8;
+      data[2] = (flags & 0x00FF0000) >> 16;
+      data[3] = (flags & 0xFF000000) >> 24;
+      data += 4;
+
+      flags = 0;
+      shifter = 0;
+    }
+
+    src += (enc->mve->width * 2) - 8;
+    block += 8;
+  }
+
+  apx->error =
+      mve_block_error_packed (enc->mve, src - 8 * enc->mve->width, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x9d (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* generic 4-color encoding (24 bytes) */
+  guint32 flags = 0;
+  guint shifter = 0;
+  guint i, x, y;
+  guint8 *data = apx->data;
+  guint16 *block = apx->block;
+
+  if (!enc->q4available) {
+    enc->q4error =
+        mve_quantize (enc->mve, src, 8, 8, 0, 4, enc->q4block, enc->q4colors);
+    enc->q4available = TRUE;
+  }
+
+  memcpy (block, enc->q4block, 128);
+
+  /* !(p[0] & 0x8000) && !(p[2] & 0x8000) */
+  GST_WRITE_UINT16_LE (&data[0], enc->q4colors[0] & ~0x8000);
+  GST_WRITE_UINT16_LE (&data[2], enc->q4colors[1]);
+  GST_WRITE_UINT16_LE (&data[4], enc->q4colors[2] & ~0x8000);
+  GST_WRITE_UINT16_LE (&data[6], enc->q4colors[3]);
+  data += 8;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; ++x, shifter += 2) {
+
+      for (i = 0; i < 3; ++i) {
+        if (*block == enc->q4colors[i])
+          break;
+      }
+
+      flags |= i << shifter;
+      ++block;
+    }
+
+    data[0] = flags & 0x000000FF;
+    data[1] = (flags & 0x0000FF00) >> 8;
+    data += 2;
+    shifter = 0;
+    flags = 0;
+  }
+
+  apx->error = enc->q4error;
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xaa (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for top and bottom half (32 bytes) */
+  guint16 cols[4];
+  guint32 flags;
+  guint i, j, x, y, shifter;
+  guint16 *block = apx->block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 2; ++i) {
+    apx->error += mve_quantize (enc->mve, src, 8, 4, i, 4, apx->block, cols);
+
+    flags = 0;
+    shifter = 0;
+
+    /* p0 & 0x8000 && p4 & 0x8000 */
+    GST_WRITE_UINT16_LE (&data[0], cols[0] | 0x8000);
+    GST_WRITE_UINT16_LE (&data[2], cols[1]);
+    GST_WRITE_UINT16_LE (&data[4], cols[2]);
+    GST_WRITE_UINT16_LE (&data[6], cols[3]);
+    data += 8;
+
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 8; ++x, shifter += 2) {
+        for (j = 0; j < 3; ++j) {
+          if (block[x] == cols[j])
+            break;
+        }
+        flags |= j << shifter;
+      }
+      block += 8;
+
+      if ((y == 1) || (y == 3)) {
+        data[0] = flags & 0x000000FF;
+        data[1] = (flags & 0x0000FF00) >> 8;
+        data[2] = (flags & 0x00FF0000) >> 16;
+        data[3] = (flags & 0xFF000000) >> 24;
+        data += 4;
+        flags = 0;
+        shifter = 0;
+      }
+    }
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xab (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for left and right half (32 bytes) */
+  guint16 cols[4];
+  guint32 flags;
+  guint i, j, x, y, shifter;
+  guint16 *block = apx->block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 2; ++i) {
+    apx->error += mve_quantize (enc->mve, src, 4, 8, i, 4, apx->block, cols);
+
+    flags = 0;
+    shifter = 0;
+
+    /* p0 & 0x8000 && !(p4 & 0x8000) */
+    GST_WRITE_UINT16_LE (&data[0], (cols[0] & ~0x8000) | (0x8000 * (i ^ 1)));
+    GST_WRITE_UINT16_LE (&data[2], cols[1]);
+    GST_WRITE_UINT16_LE (&data[4], cols[2]);
+    GST_WRITE_UINT16_LE (&data[6], cols[3]);
+    data += 8;
+
+    for (y = 0; y < 8; ++y) {
+      for (x = 0; x < 4; ++x, shifter += 2) {
+        for (j = 0; j < 3; ++j) {
+          if (block[x] == cols[j])
+            break;
+        }
+        flags |= j << shifter;
+      }
+      block += 8;
+
+      if ((y == 3) || (y == 7)) {
+        data[0] = flags & 0x000000FF;
+        data[1] = (flags & 0x0000FF00) >> 8;
+        data[2] = (flags & 0x00FF0000) >> 16;
+        data[3] = (flags & 0xFF000000) >> 24;
+        data += 4;
+        flags = 0;
+        shifter = 0;
+      }
+    }
+    block = apx->block + 4;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xac (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for each 4x4 quadrant (48 bytes) */
+  guint16 cols[4];
+  guint32 flags;
+  guint i, j, x, y, shifter;
+  guint16 *block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 4; ++i) {
+    apx->error +=
+        mve_quantize (enc->mve, src, 4, 4, ((i & 1) << 1) | ((i & 2) >> 1), 4,
+        apx->block, cols);
+
+    /* !(p0 & 0x8000) */
+    GST_WRITE_UINT16_LE (&data[0], cols[0] & ~0x8000);
+    GST_WRITE_UINT16_LE (&data[2], cols[1]);
+    GST_WRITE_UINT16_LE (&data[4], cols[2]);
+    GST_WRITE_UINT16_LE (&data[6], cols[3]);
+
+    block = apx->block + ((i / 2) * 4) + ((i % 2) * 32);
+    flags = 0;
+    shifter = 0;
+
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 4; ++x, shifter += 2) {
+        for (j = 0; j < 3; ++j) {
+          if (block[x] == cols[j])
+            break;
+        }
+        flags |= j << shifter;
+      }
+      block += 8;
+    }
+
+    data[8] = flags & 0x000000FF;
+    data[9] = (flags & 0x0000FF00) >> 8;
+    data[10] = (flags & 0x00FF0000) >> 16;
+    data[11] = (flags & 0xFF000000) >> 24;
+    data += 12;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xb (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 64-color encoding (each pixel in block is a different color) (128 bytes) */
+  guint i;
+
+  apx->error = 0;
+
+  mve_store_block (enc->mve, src, apx->block);
+  for (i = 0; i < 64; ++i)
+    GST_WRITE_UINT16_LE (&apx->data[i << 1], apx->block[i]);
+
+  return 0;
+}
+
+static guint32
+mve_encode_0xc (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 16-color block encoding: each 2x2 block is a different color (32 bytes) */
+  guint i = 0, x, y;
+  const guint w = enc->mve->width;
+  guint16 r, g, b;
+
+  /* calculate median color for each 2x2 block */
+  for (y = 0; y < 4; ++y) {
+    for (x = 0; x < 4; ++x) {
+      r = MVE_RVAL (src[0]) + MVE_RVAL (src[1]) +
+          MVE_RVAL (src[w]) + MVE_RVAL (src[w + 1]) + 2;
+      g = MVE_GVAL (src[0]) + MVE_GVAL (src[1]) +
+          MVE_GVAL (src[w]) + MVE_GVAL (src[w + 1]) + 2;
+      b = MVE_BVAL (src[0]) + MVE_BVAL (src[1]) +
+          MVE_BVAL (src[w]) + MVE_BVAL (src[w + 1]) + 2;
+      apx->block[i] = apx->block[i + 1] = apx->block[i + 2] =
+          apx->block[i + 3] = MVE_COL (r >> 2, g >> 2, b >> 2);
+      GST_WRITE_UINT16_LE (&apx->data[i >> 1], apx->block[i]);
+
+      i += 4;
+      src += 2;
+    }
+    src += (w * 2) - 8;
+  }
+
+  apx->error = mve_block_error_packed (enc->mve, src - (8 * w), apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xd (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color block encoding: each 4x4 block is a different color (8 bytes) */
+  guint i, x, y;
+  guint16 *block;
+
+  /* calculate median color for each 4x4 block */
+  for (i = 0; i < 4; ++i) {
+    guint16 median =
+        mve_median_sub (enc->mve, src, 4, 4, ((i & 1) << 1) | ((i & 2) >> 1));
+
+    block = apx->block + ((i / 2) * 4) + ((i % 2) * 32);
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 4; ++x) {
+        block[x] = median;
+      }
+      block += 8;
+    }
+
+    GST_WRITE_UINT16_LE (&apx->data[i << 1], median);
+  }
+
+  apx->error = mve_block_error_packed (enc->mve, src, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xe (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 1-color encoding: the whole block is 1 solid color (2 bytes) */
+  guint i;
+  guint16 median = mve_median (enc->mve, src);
+
+  for (i = 0; i < 64; ++i)
+    apx->block[i] = median;
+
+  apx->error = mve_block_error_packed (enc->mve, src, apx->block);
+  GST_WRITE_UINT16_LE (apx->data, median);
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xf (GstMveEncoderData * enc, const guint16 * src,
+    GstMveApprox * apx)
+{
+  /* 2 colors dithered encoding (4 bytes) */
+  guint i, x, y;
+  guint32 r[2] = { 0 }, g[2] = {
+  0}, b[2] = {
+  0};
+  guint16 col[2];
+
+  /* find medians for both colors */
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; x += 2) {
+      guint16 p = src[x];
+
+      r[y & 1] += MVE_RVAL (p);
+      g[y & 1] += MVE_GVAL (p);
+      b[y & 1] += MVE_BVAL (p);
+
+      p = src[x + 1];
+      r[(y & 1) ^ 1] += MVE_RVAL (p);
+      g[(y & 1) ^ 1] += MVE_GVAL (p);
+      b[(y & 1) ^ 1] += MVE_BVAL (p);
+    }
+    src += enc->mve->width;
+  }
+  col[0] = MVE_COL ((r[0] + 16) / 32, (g[0] + 16) / 32, (b[0] + 16) / 32);
+  col[1] = MVE_COL ((r[1] + 16) / 32, (g[1] + 16) / 32, (b[1] + 16) / 32);
+
+  /* store block after encoding */
+  for (i = 0, y = 0; y < 8; ++y) {
+    for (x = 0; x < 4; ++x) {
+      apx->block[i++] = col[y & 1];
+      apx->block[i++] = col[(y & 1) ^ 1];
+    }
+  }
+
+  GST_WRITE_UINT16_LE (&apx->data[0], col[0]);
+  GST_WRITE_UINT16_LE (&apx->data[2], col[1]);
+  apx->error = mve_block_error_packed (enc->mve,
+      src - (8 * enc->mve->width), apx->block);
+  return apx->error;
+}
+
+/* all available encodings in the preferred order,
+   i.e. in ascending encoded size */
+static const GstMveEncoding mve_encodings[] = {
+  {0x1, 0, mve_encode_0x1},
+  {0x0, 0, mve_encode_0x0},
+  {0x3, 1, mve_encode_0x3},
+  {0x4, 1, mve_encode_0x4},
+  {0x2, 1, mve_encode_0x2},
+  {0xe, 2, mve_encode_0xe},
+  {0x5, 2, mve_encode_0x5},
+  {0xf, 4, mve_encode_0xf},
+  {0x7, 6, mve_encode_0x7a},
+  {0xd, 8, mve_encode_0xd},
+  {0x7, 12, mve_encode_0x7b},
+  {0x9, 12, mve_encode_0x9a},
+  {0x9, 16, mve_encode_0x9b},
+  {0x9, 16, mve_encode_0x9c},
+  {0x8, 16, mve_encode_0x8a},
+  {0x8, 16, mve_encode_0x8b},
+  {0x8, 24, mve_encode_0x8c},
+  {0x9, 24, mve_encode_0x9d},
+  {0xc, 32, mve_encode_0xc},
+  {0xa, 32, mve_encode_0xaa},
+  {0xa, 32, mve_encode_0xab},
+  {0xa, 48, mve_encode_0xac},
+  {0xb, 128, mve_encode_0xb}
+};
+
+static gboolean
+mve_reorder_solution (GArray ** solution, guint16 n)
+{
+  /* do a binary search to find the position to reinsert the modified element */
+  /* the block we need to reconsider is always at position 0 */
+  /* return TRUE if this block only has 1 encoding left and can be dropped */
+  if (mve_comp_solution (&solution[0], &solution[1]) <= 0)
+    return FALSE;               /* already sorted */
+
+  else if (solution[0]->len <= 1)
+    /* drop this element from further calculations since we cannot improve here */
+    return TRUE;
+
+  else {
+    /* we know the error value can only get worse, so we can actually start at 1 */
+    guint lower = 1;
+    guint upper = n - 1;
+    gint cmp;
+    guint idx = 0;
+
+    while (upper > lower) {
+      idx = lower + ((upper - lower) / 2);
+
+      cmp = mve_comp_solution (&solution[0], &solution[idx]);
+
+      if (cmp < 0) {
+        upper = idx;
+      } else if (cmp > 0) {
+        lower = ++idx;
+      } else {
+        upper = lower = idx;
+      }
+    }
+
+    if (idx > 0) {
+      /* rearrange array members in new order */
+      GArray *a = solution[0];
+
+      memcpy (&solution[0], &solution[1], sizeof (GArray *) * idx);
+      solution[idx] = a;
+    }
+  }
+  return FALSE;
+}
+
+static guint32
+gst_mve_find_solution (GArray ** approx, guint16 n, guint32 size, guint16 max)
+{
+  /* build an array of approximations we can shuffle around */
+  GstMveApprox *sol_apx;
+  GArray **solution = g_malloc (sizeof (GArray *) * n);
+  GArray **current = solution;
+
+  memcpy (solution, approx, sizeof (GArray *) * n);
+
+  qsort (solution, n, sizeof (GArray *), mve_comp_solution);
+
+  do {
+    /* array is now sorted by error of the next to optimal approximation;
+       drop optimal approximation for the best block */
+
+    /* unable to reduce size further */
+    if (current[0]->len <= 1)
+      break;
+
+    sol_apx = &g_array_index (current[0], GstMveApprox, current[0]->len - 1);
+    size -= mve_encodings[sol_apx->type].size;
+    g_array_remove_index_fast (current[0], current[0]->len - 1);
+    sol_apx = &g_array_index (current[0], GstMveApprox, current[0]->len - 1);
+    size += mve_encodings[sol_apx->type].size;
+
+    if (mve_reorder_solution (current, n)) {
+      ++current;
+      --n;
+    }
+  } while (size > max);
+
+  g_free (solution);
+
+  return size;
+}
+
+GstFlowReturn
+mve_encode_frame16 (GstMveMux * mve, GstBuffer * frame, guint16 max_data)
+{
+  guint16 *src;
+  GstFlowReturn ret = GST_FLOW_ERROR;
+  guint8 *cm = mve->chunk_code_map;
+  GByteArray *pstream;
+  GArray **approx;
+  GstMveApprox apx;
+  GstMveEncoderData enc;
+  const guint16 blocks = (mve->width * mve->height) / 64;
+  guint32 encoded_size = 2;     /* two initial bytes for the offset */
+  guint i = 0, x, y;
+
+  src = (guint16 *) GST_BUFFER_DATA (frame);
+
+  approx = g_malloc (sizeof (GArray *) * blocks);
+
+  enc.mve = mve;
+
+  for (enc.y = 0; enc.y < mve->height; enc.y += 8) {
+    for (enc.x = 0; enc.x < mve->width; enc.x += 8) {
+      guint32 err, last_err = MVE_APPROX_MAX_ERROR;
+      guint type = 0;
+      guint best = 0;
+
+      enc.q2available = enc.q4available = FALSE;
+      approx[i] = g_array_new (FALSE, FALSE, sizeof (GstMveApprox));
+
+      do {
+        err = mve_encodings[type].approx (&enc, src, &apx);
+
+        if (err < last_err) {
+          apx.type = best = type;
+          g_array_append_val (approx[i], apx);
+          last_err = err;
+        }
+
+        ++type;
+      } while (last_err != 0);
+
+      encoded_size += mve_encodings[best].size;
+      ++i;
+      src += 8;
+    }
+    src += 7 * mve->width;
+  }
+
+  /* find best solution with size constraints */
+  GST_DEBUG_OBJECT (mve, "encoded frame %u in %ld bytes (lossless)",
+      mve->video_frames + 1, encoded_size);
+
+#if 0
+  /* FIXME */
+  src = (guint16 *) GST_BUFFER_DATA (frame);
+  for (i = 0, y = 0; y < mve->height; y += 8) {
+    for (x = 0; x < mve->width; x += 8, ++i) {
+      GstMveApprox *sol =
+          &g_array_index (approx[i], GstMveApprox, approx[i]->len - 1);
+      guint opcode = mve_encodings[sol->type].opcode;
+      guint j, k;
+
+      if (sol->error > 0)
+        GST_WARNING_OBJECT (mve, "error is %lu for %d/%d (0x%x)", sol->error, x,
+            y, opcode);
+
+      for (j = 0; j < 8; ++j) {
+        guint16 *o = src + j * mve->width;
+        guint16 *c = sol->block + j * 8;
+
+        if (memcmp (o, c, 16)) {
+          GST_WARNING_OBJECT (mve, "opcode 0x%x (type %d) at %d/%d, line %d:",
+              opcode, sol->type, x, y, j + 1);
+          for (k = 0; k < 8; ++k) {
+            o = src + k * mve->width;
+            c = sol->block + k * 8;
+            GST_WARNING_OBJECT (mve,
+                "%d should be: %4d  %4d  %4d  %4d  %4d  %4d  %4d  %4d", k, o[0],
+                o[1], o[2], o[3], o[4], o[5], o[6], o[7]);
+            GST_WARNING_OBJECT (mve,
+                "%d but is   : %4d  %4d  %4d  %4d  %4d  %4d  %4d  %4d", k, c[0],
+                c[1], c[2], c[3], c[4], c[5], c[6], c[7]);
+          }
+        }
+      }
+      src += 8;
+    }
+    src += 7 * mve->width;
+  }
+#endif
+
+  if (encoded_size > max_data) {
+    encoded_size =
+        gst_mve_find_solution (approx, blocks, encoded_size, max_data);
+    if (encoded_size > max_data) {
+      GST_ERROR_OBJECT (mve, "unable to compress frame to less than %d bytes",
+          encoded_size);
+      for (i = 0; i < blocks; ++i)
+        g_array_free (approx[i], TRUE);
+
+      goto done;
+    }
+    GST_DEBUG_OBJECT (mve, "compressed frame %u to %ld bytes (lossy)",
+        mve->video_frames + 1, encoded_size);
+  }
+
+  mve->chunk_video = g_byte_array_sized_new (encoded_size);
+  /* reserve two bytes for the offset pointer we'll fill in later */
+  g_byte_array_set_size (mve->chunk_video, 2);
+
+  pstream = g_byte_array_new ();
+
+  /* encode */
+  src = (guint16 *) GST_BUFFER_DATA (frame);
+  for (i = 0, y = 0; y < mve->height; y += 8) {
+    for (x = 0; x < mve->width; x += 8, ++i) {
+      GstMveApprox *sol =
+          &g_array_index (approx[i], GstMveApprox, approx[i]->len - 1);
+      guint opcode = mve_encodings[sol->type].opcode;
+      GByteArray *dest;
+
+      if (opcode >= 0x2 && opcode <= 0x4)
+        dest = pstream;
+      else
+        dest = mve->chunk_video;
+
+      g_byte_array_append (dest, sol->data, mve_encodings[sol->type].size);
+
+      if (i & 1) {
+        *cm |= opcode << 4;
+        ++cm;
+      } else
+        *cm = opcode;
+
+      /* modify the frame to match the image we actually encoded */
+      if (sol->error > 0)
+        mve_restore_block (mve, src, sol->block);
+
+      src += 8;
+      g_array_free (approx[i], TRUE);
+    }
+    src += 7 * mve->width;
+  }
+
+  /* now update the offset */
+  GST_WRITE_UINT16_LE (mve->chunk_video->data, mve->chunk_video->len);
+  g_byte_array_append (mve->chunk_video, pstream->data, pstream->len);
+  g_byte_array_free (pstream, TRUE);
+
+  ret = GST_FLOW_OK;
+
+done:
+  g_free (approx);
+
+  return ret;
+}
diff --git a/gst/mve/mvevideoenc8.c b/gst/mve/mvevideoenc8.c
new file mode 100644 (file)
index 0000000..78e3eb1
--- /dev/null
@@ -0,0 +1,1733 @@
+/*
+ * Interplay MVE video encoder (8 bit)
+ * Copyright (C) 2006 Jens Granseuer <jensgr@gmx.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "mve.h"
+#include "gstmvemux.h"
+
+typedef struct _GstMveEncoderData GstMveEncoderData;
+typedef struct _GstMveEncoding GstMveEncoding;
+typedef struct _GstMveApprox GstMveApprox;
+typedef struct _GstMveQuant GstMveQuant;
+
+#define MVE_RMASK   0x00ff0000
+#define MVE_GMASK   0x0000ff00
+#define MVE_BMASK   0x000000ff
+#define MVE_RSHIFT  16
+#define MVE_GSHIFT  8
+#define MVE_BSHIFT  0
+
+#define MVE_RVAL(p)     (((p) & MVE_RMASK) >> MVE_RSHIFT)
+#define MVE_GVAL(p)     (((p) & MVE_GMASK) >> MVE_GSHIFT)
+#define MVE_BVAL(p)     (((p) & MVE_BMASK) >> MVE_BSHIFT)
+#define MVE_COL(r,g,b)  (((r) << MVE_RSHIFT) | ((g) << MVE_GSHIFT) | ((b) << MVE_BSHIFT))
+
+struct _GstMveEncoderData
+{
+  GstMveMux *mve;
+  /* current position in frame */
+  guint16 x, y;
+
+  /* palette for current frame */
+  const guint32 *palette;
+
+  /* commonly used quantization results
+     (2 and 4 colors) for the current block */
+  guint8 q2block[64];
+  guint8 q2colors[2];
+  guint32 q2error;
+  gboolean q2available;
+
+  guint8 q4block[64];
+  guint8 q4colors[4];
+  guint32 q4error;
+  gboolean q4available;
+};
+
+struct _GstMveEncoding
+{
+  guint8 opcode;
+  guint8 size;
+    guint32 (*approx) (GstMveEncoderData * enc, const guint8 * src,
+      GstMveApprox * res);
+};
+
+#define MVE_APPROX_MAX_ERROR  G_MAXUINT32
+
+struct _GstMveApprox
+{
+  guint32 error;
+  guint8 type;
+  guint8 data[64];              /* max 64 bytes encoded per block */
+  guint8 block[64];             /* block in final image */
+};
+
+struct _GstMveQuant
+{
+  guint32 col;
+  guint16 r_total, g_total, b_total;
+  guint8 r, g, b;
+  guint8 hits, hits_last;
+  guint32 max_error;
+  guint32 max_miss;
+};
+
+#define mve_median(mve, src) mve_median_sub ((mve), (src), 8, 8, 0)
+#define mve_color_dist(c1, c2) \
+        mve_color_dist_rgb (MVE_RVAL (c1), MVE_GVAL (c1), MVE_BVAL (c1), \
+                            MVE_RVAL (c2), MVE_GVAL (c2), MVE_BVAL (c2));
+#define mve_color_dist2(c, r, g, b)  \
+        mve_color_dist_rgb (MVE_RVAL (c), MVE_GVAL (c), MVE_BVAL (c), (r), (g), (b))
+
+
+/* comparison function for qsort() */
+static int
+mve_comp_solution (const void *a, const void *b)
+{
+  const GArray *aa = *((GArray **) a);
+  const GArray *bb = *((GArray **) b);
+
+  if (aa->len <= 1)
+    return G_MAXINT;
+  else if (bb->len <= 1)
+    return G_MININT;
+  else
+    return g_array_index (aa, GstMveApprox, aa->len - 2).error -
+        g_array_index (bb, GstMveApprox, bb->len - 2).error;
+}
+
+static inline guint32
+mve_color_dist_rgb (guint8 r1, guint8 g1, guint8 b1,
+    guint8 r2, guint8 g2, guint8 b2)
+{
+  /* euclidean distance (minus sqrt) */
+  gint dr = r1 - r2;
+  gint dg = g1 - g2;
+  gint db = b1 - b2;
+
+  return dr * dr + dg * dg + db * db;
+}
+
+static guint8
+mve_find_pal_color (const guint32 * pal, guint32 col)
+{
+  /* find the closest matching color in the palette */
+  guint i;
+  guint8 best = 0;
+  const guint8 r = MVE_RVAL (col), g = MVE_GVAL (col), b = MVE_BVAL (col);
+  guint32 ebest = MVE_APPROX_MAX_ERROR;
+
+  for (i = 0; i < MVE_PALETTE_COUNT; ++i, ++pal) {
+    guint32 e = mve_color_dist2 (*pal, r, g, b);
+
+    if (e < ebest) {
+      ebest = e;
+      best = i;
+
+      if (ebest == 0)
+        break;
+    }
+  }
+
+  return best;
+}
+
+static guint8
+mve_find_pal_color2 (const guint32 * pal, const guint8 * subset, guint32 col,
+    guint size)
+{
+  /* find the closest matching color in the partial indexed palette */
+  guint i;
+  guint8 best = 0;
+  const guint8 r = MVE_RVAL (col), g = MVE_GVAL (col), b = MVE_BVAL (col);
+  guint32 ebest = MVE_APPROX_MAX_ERROR;
+
+  for (i = 0; i < size; ++i) {
+    guint32 e = mve_color_dist2 (pal[subset[i]], r, g, b);
+
+    if (e < ebest) {
+      ebest = e;
+      best = subset[i];
+
+      if (ebest == 0)
+        break;
+    }
+  }
+
+  return best;
+}
+
+static void
+mve_map_to_palette (const GstMveEncoderData * enc, const guint8 * colors,
+    const guint8 * data, guint8 * dest, guint w, guint h, guint ncols)
+{
+  guint x, y;
+
+  for (y = 0; y < h; ++y) {
+    for (x = 0; x < w; ++x) {
+      dest[x] =
+          mve_find_pal_color2 (enc->palette, colors, enc->palette[data[x]],
+          ncols);
+    }
+    data += enc->mve->width;
+    dest += 8;
+  }
+}
+
+/* compute average color in a sub-block */
+static guint8
+mve_median_sub (const GstMveEncoderData * enc, const guint8 * src, guint w,
+    guint h, guint n)
+{
+  guint x, y;
+  const guint max = w * h, max2 = max >> 1;
+  guint32 r_total = max2, g_total = max2, b_total = max2;
+
+  src += ((n * w) % 8) + (((n * (8 - h)) / (12 - w)) * h * enc->mve->width);
+
+  for (y = 0; y < h; ++y) {
+    for (x = 0; x < w; ++x) {
+      guint32 p = enc->palette[src[x]];
+
+      r_total += MVE_RVAL (p);
+      g_total += MVE_GVAL (p);
+      b_total += MVE_BVAL (p);
+    }
+    src += enc->mve->width;
+  }
+
+  return mve_find_pal_color (enc->palette,
+      MVE_COL (r_total / max, g_total / max, b_total / max));
+}
+
+static void
+mve_quant_init (const GstMveEncoderData * enc, GstMveQuant * q,
+    guint n_clusters, const guint8 * data, guint w, guint h)
+{
+  guint i;
+  guint x, y;
+  guint32 cols[4];
+  guint16 val[2];
+
+  /* init first cluster with lowest (darkest), second with highest (lightest)
+     color. if we need 4 clusters, fill in first and last color in the block
+     and hope they make for a good distribution */
+  cols[0] = cols[1] = cols[2] = enc->palette[data[0]];
+  cols[3] = enc->palette[data[(h - 1) * enc->mve->width + w - 1]];
+
+  /* favour red over green and blue */
+  val[0] = val[1] =
+      (MVE_RVAL (cols[0]) << 1) + MVE_GVAL (cols[0]) + MVE_BVAL (cols[0]);
+
+  for (y = 0; y < h; ++y) {
+    for (x = 0; x < w; ++x) {
+      guint32 c = enc->palette[data[x]];
+
+      if ((c != cols[0]) && (c != cols[1])) {
+        guint v = (MVE_RVAL (c) << 1) + MVE_GVAL (c) + MVE_BVAL (c);
+
+        if (v < val[0]) {
+          val[0] = v;
+          cols[0] = c;
+        } else if (v > val[1]) {
+          val[1] = v;
+          cols[1] = c;
+        }
+      }
+    }
+    data += enc->mve->width;
+  }
+
+  for (i = 0; i < n_clusters; ++i) {
+    q[i].col = cols[i];
+    q[i].r = MVE_RVAL (cols[i]);
+    q[i].g = MVE_GVAL (cols[i]);
+    q[i].b = MVE_BVAL (cols[i]);
+    q[i].r_total = q[i].g_total = q[i].b_total = 0;
+    q[i].hits = q[i].hits_last = 0;
+    q[i].max_error = 0;
+    q[i].max_miss = 0;
+  }
+}
+
+static gboolean
+mve_quant_update_clusters (GstMveQuant * q, guint n_clusters)
+{
+  gboolean changed = FALSE;
+  guint i;
+
+  for (i = 0; i < n_clusters; ++i) {
+    if (q[i].hits > 0) {
+      guint32 means = MVE_COL ((q[i].r_total + q[i].hits / 2) / q[i].hits,
+          (q[i].g_total + q[i].hits / 2) / q[i].hits,
+          (q[i].b_total + q[i].hits / 2) / q[i].hits);
+
+      if ((means != q[i].col) || (q[i].hits != q[i].hits_last))
+        changed = TRUE;
+
+      q[i].col = means;
+      q[i].r_total = q[i].g_total = q[i].b_total = 0;
+    } else {
+      guint j;
+      guint32 max_err = 0;
+      GstMveQuant *worst = NULL;
+
+      /* try to replace unused cluster with a better representative */
+      for (j = 0; j < n_clusters; ++j) {
+        if (q[j].max_error > max_err) {
+          worst = &q[j];
+          max_err = worst->max_error;
+        }
+      }
+      if (worst) {
+        q[i].col = worst->max_miss;
+        worst->max_error = 0;
+        changed = TRUE;
+      }
+    }
+
+    q[i].r = MVE_RVAL (q[i].col);
+    q[i].g = MVE_GVAL (q[i].col);
+    q[i].b = MVE_BVAL (q[i].col);
+    q[i].hits_last = q[i].hits;
+    q[i].hits = 0;
+  }
+  for (i = 0; i < n_clusters; ++i) {
+    q[i].max_error = 0;
+  }
+
+  return changed;
+}
+
+/* quantize a sub-block using a k-means algorithm */
+static guint32
+mve_quantize (const GstMveEncoderData * enc, const guint8 * src,
+    guint w, guint h, guint n, guint ncols, guint8 * dest, guint8 * cols)
+{
+  guint x, y, i;
+  GstMveQuant q[4];
+  const guint8 *data;
+  guint32 error;
+
+  g_assert (n <= 4 && ncols <= 4);
+
+  src += ((n * w) % 8) + (((n * (8 - h)) / (12 - w)) * h * enc->mve->width);
+  dest += ((n * w) % 8) + (((n * (8 - h)) / (12 - w)) * h * 8);
+
+  mve_quant_init (enc, q, ncols, src, w, h);
+
+  do {
+    data = src;
+    error = 0;
+
+    /* for each pixel find the closest cluster */
+    for (y = 0; y < h; ++y) {
+      for (x = 0; x < w; ++x) {
+        guint32 c = enc->palette[data[x]];
+        guint8 r = MVE_RVAL (c), g = MVE_GVAL (c), b = MVE_BVAL (c);
+        guint32 minerr = MVE_APPROX_MAX_ERROR, err;
+        GstMveQuant *best = NULL;
+
+        for (i = 0; i < ncols; ++i) {
+          err = mve_color_dist_rgb (r, g, b, q[i].r, q[i].g, q[i].b);
+
+          if (err < minerr) {
+            minerr = err;
+            best = &q[i];
+          }
+        }
+
+        ++best->hits;
+        best->r_total += r;
+        best->g_total += g;
+        best->b_total += b;
+
+        if (minerr > best->max_error) {
+          best->max_error = minerr;
+          best->max_miss = c;
+        }
+
+        error += minerr;
+      }
+      data += enc->mve->width;
+    }
+  } while (mve_quant_update_clusters (q, ncols));
+
+  /* fill cols array with result colors */
+  for (i = 0; i < ncols; ++i)
+    cols[i] = mve_find_pal_color (enc->palette, q[i].col);
+
+  /* make sure we have unique colors in slots 0/1 and 2/3 */
+  if (cols[0] == cols[1])
+    ++cols[1];
+  if ((ncols > 2) && (cols[2] == cols[3]))
+    ++cols[3];
+
+  /* generate the resulting quantized block */
+  mve_map_to_palette (enc, cols, src, dest, w, h, ncols);
+
+  return error;
+}
+
+static guint32
+mve_block_error (const GstMveEncoderData * enc, const guint8 * b1,
+    const guint8 * b2, guint32 threshold)
+{
+  /* compute error between two blocks in a frame */
+  guint32 e = 0;
+  guint x, y;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; ++x) {
+      e += mve_color_dist (enc->palette[*b1], enc->palette[*b2]);
+
+      /* using a threshold to return early gives a huge performance bonus */
+      if (e >= threshold)
+        return MVE_APPROX_MAX_ERROR;
+      ++b1;
+      ++b2;
+    }
+
+    b1 += enc->mve->width - 8;
+    b2 += enc->mve->width - 8;
+  }
+
+  return e;
+}
+
+static guint32
+mve_block_error_packed (const GstMveEncoderData * enc, const guint8 * block,
+    const guint8 * scratch)
+{
+  /* compute error between a block in a frame and a (continuous) scratch pad */
+  guint32 e = 0;
+  guint x, y;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; ++x) {
+      guint32 c1 = enc->palette[block[x]], c2 = enc->palette[scratch[x]];
+
+      e += mve_color_dist (c1, c2);
+    }
+    block += enc->mve->width;
+    scratch += 8;
+  }
+
+  return e;
+}
+
+static void
+mve_store_block (const GstMveMux * mve, const guint8 * block, guint8 * scratch)
+{
+  /* copy block from frame to a (continuous) scratch pad */
+  guint y;
+
+  for (y = 0; y < 8; ++y) {
+    memcpy (scratch, block, 8);
+    block += mve->width;
+    scratch += 8;
+  }
+}
+
+static void
+mve_restore_block (const GstMveMux * mve, guint8 * block,
+    const guint8 * scratch)
+{
+  /* copy block from scratch pad to frame */
+  guint y;
+
+  for (y = 0; y < 8; ++y) {
+    memcpy (block, scratch, 8);
+    block += mve->width;
+    scratch += 8;
+  }
+}
+
+
+static guint32
+mve_try_vector (GstMveEncoderData * enc, const guint8 * src,
+    const guint8 * frame, gint pn, GstMveApprox * apx)
+{
+  /* try to locate a similar 8x8 block in the given frame using a motion vector */
+  guint i;
+  gint dx, dy;
+  gint fx, fy;
+  guint32 err;
+
+  apx->error = MVE_APPROX_MAX_ERROR;
+
+  for (i = 0; i < 256; ++i) {
+    if (i < 56) {
+      dx = 8 + (i % 7);
+      dy = i / 7;
+    } else {
+      dx = -14 + ((i - 56) % 29);
+      dy = 8 + ((i - 56) / 29);
+    }
+
+    fx = enc->x + dx * pn;
+    fy = enc->y + dy * pn;
+
+    if ((fx >= 0) && (fy >= 0) && (fx + 8 <= enc->mve->width)
+        && (fy + 8 <= enc->mve->height)) {
+      err =
+          mve_block_error (enc, src, frame + fy * enc->mve->width + fx,
+          apx->error);
+      if (err < apx->error) {
+        apx->data[0] = i;
+        mve_store_block (enc->mve, frame + fy * enc->mve->width + fx,
+            apx->block);
+        apx->error = err;
+        if (err == 0)
+          return 0;
+      }
+    }
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x0 (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* copy a block from the last frame (0 bytes) */
+  if (enc->mve->last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  mve_store_block (enc->mve,
+      GST_BUFFER_DATA (enc->mve->last_frame) +
+      enc->y * enc->mve->width + enc->x, apx->block);
+  apx->error = mve_block_error_packed (enc, src, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x1 (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* copy a block from the second to last frame (0 bytes) */
+  if (enc->mve->second_last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  mve_store_block (enc->mve,
+      GST_BUFFER_DATA (enc->mve->second_last_frame) +
+      enc->y * enc->mve->width + enc->x, apx->block);
+  apx->error = mve_block_error_packed (enc, src, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x2 (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* copy block from 2 frames ago using a motion vector (1 byte) */
+  if (enc->mve->quick_encoding || enc->mve->second_last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  apx->error = mve_try_vector (enc, src,
+      GST_BUFFER_DATA (enc->mve->second_last_frame), 1, apx);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x3 (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* copy 8x8 block from current frame from an up/left block (1 byte) */
+  if (enc->mve->quick_encoding)
+    return MVE_APPROX_MAX_ERROR;
+
+  apx->error = mve_try_vector (enc, src,
+      src - enc->mve->width * enc->y - enc->x, -1, apx);
+  return apx->error;
+}
+
+
+static guint32
+mve_encode_0x4 (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* copy a block from previous frame using a motion vector (-8/-8 to +7/+7) (1 byte) */
+  const GstMveMux *mve = enc->mve;
+  guint32 err;
+  const guint8 *frame;
+  gint x1, x2, xi, y1, y2, yi;
+
+  if (mve->last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  frame = GST_BUFFER_DATA (mve->last_frame);
+
+  x1 = enc->x - 8;
+  x2 = enc->x + 7;
+  if (x1 < 0)
+    x1 = 0;
+  else if (x2 + 8 > mve->width)
+    x2 = mve->width - 8;
+
+  y1 = enc->y - 8;
+  y2 = enc->y + 7;
+  if (y1 < 0)
+    y1 = 0;
+  else if (y2 + 8 > mve->height)
+    y2 = mve->height - 8;
+
+  apx->error = MVE_APPROX_MAX_ERROR;
+
+  for (yi = y1; yi <= y2; ++yi) {
+    guint yoff = yi * mve->width;
+
+    for (xi = x1; xi <= x2; ++xi) {
+      err = mve_block_error (enc, src, frame + yoff + xi, apx->error);
+      if (err < apx->error) {
+        apx->data[0] = ((xi - enc->x + 8) & 0xF) | ((yi - enc->y + 8) << 4);
+        mve_store_block (mve, frame + yoff + xi, apx->block);
+        apx->error = err;
+        if (err == 0)
+          return 0;
+      }
+    }
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x5 (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* copy a block from previous frame using a motion vector
+     (-128/-128 to +127/+127) (2 bytes) */
+  const GstMveMux *mve = enc->mve;
+  guint32 err;
+  const guint8 *frame;
+  gint x1, x2, xi, y1, y2, yi;
+
+  if (mve->quick_encoding || mve->last_frame == NULL)
+    return MVE_APPROX_MAX_ERROR;
+
+  frame = GST_BUFFER_DATA (mve->last_frame);
+
+  x1 = enc->x - 128;
+  x2 = enc->x + 127;
+  if (x1 < 0)
+    x1 = 0;
+  if (x2 + 8 > mve->width)
+    x2 = mve->width - 8;
+
+  y1 = enc->y - 128;
+  y2 = enc->y + 127;
+  if (y1 < 0)
+    y1 = 0;
+  if (y2 + 8 > mve->height)
+    y2 = mve->height - 8;
+
+  apx->error = MVE_APPROX_MAX_ERROR;
+
+  for (yi = y1; yi <= y2; ++yi) {
+    gint yoff = yi * mve->width;
+
+    for (xi = x1; xi <= x2; ++xi) {
+      err = mve_block_error (enc, src, frame + yoff + xi, apx->error);
+      if (err < apx->error) {
+        apx->data[0] = xi - enc->x;
+        apx->data[1] = yi - enc->y;
+        mve_store_block (mve, frame + yoff + xi, apx->block);
+        apx->error = err;
+        if (err == 0)
+          return 0;
+      }
+    }
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x7a (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 2-color encoding for 2x2 solid blocks (4 bytes) */
+  guint32 pix[4];
+  guint8 mean;
+  guint32 e1, e2;
+  guint x, y;
+  guint8 r[2], g[2], b[2], rb, gb, bb;
+  guint8 *block = apx->block;
+  guint16 mask = 0x0001;
+  guint16 flags = 0;
+
+  /* calculate mean colors for the entire block */
+  if (!enc->q2available) {
+    enc->q2error =
+        mve_quantize (enc, src, 8, 8, 0, 2, enc->q2block, enc->q2colors);
+    enc->q2available = TRUE;
+  }
+
+  /* p0 > p1 */
+  apx->data[0] = MAX (enc->q2colors[0], enc->q2colors[1]);
+  apx->data[1] = MIN (enc->q2colors[0], enc->q2colors[1]);
+
+  for (x = 0; x < 2; ++x) {
+    r[x] = MVE_RVAL (enc->palette[apx->data[x]]);
+    g[x] = MVE_GVAL (enc->palette[apx->data[x]]);
+    b[x] = MVE_BVAL (enc->palette[apx->data[x]]);
+  }
+
+  /* calculate mean colors for each 2x2 block and map to global colors */
+  for (y = 0; y < 4; ++y) {
+    for (x = 0; x < 4; ++x, mask <<= 1) {
+      pix[0] = enc->palette[src[0]];
+      pix[1] = enc->palette[src[1]];
+      pix[2] = enc->palette[src[enc->mve->width]];
+      pix[3] = enc->palette[src[enc->mve->width + 1]];
+
+      rb = (MVE_RVAL (pix[0]) + MVE_RVAL (pix[1]) + MVE_RVAL (pix[2]) +
+          MVE_RVAL (pix[3]) + 2) / 4;
+      gb = (MVE_GVAL (pix[0]) + MVE_GVAL (pix[1]) + MVE_GVAL (pix[2]) +
+          MVE_GVAL (pix[3]) + 2) / 4;
+      bb = (MVE_BVAL (pix[0]) + MVE_BVAL (pix[1]) + MVE_BVAL (pix[2]) +
+          MVE_BVAL (pix[3]) + 2) / 4;
+
+      e1 = mve_color_dist_rgb (rb, gb, bb, r[0], g[0], b[0]);
+      e2 = mve_color_dist_rgb (rb, gb, bb, r[1], g[1], b[1]);
+
+      if (e1 > e2) {
+        mean = apx->data[1];
+        flags |= mask;
+      } else {
+        mean = apx->data[0];
+      }
+
+      block[0] = block[1] = block[8] = block[9] = mean;
+
+      src += 2;
+      block += 2;
+    }
+    src += (enc->mve->width * 2) - 8;
+    block += 8;
+  }
+
+  apx->data[2] = flags & 0x00FF;
+  apx->data[3] = (flags & 0xFF00) >> 8;
+
+  apx->error =
+      mve_block_error_packed (enc, src - enc->mve->width * 8, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x7b (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* generic 2-color encoding (10 bytes) */
+  guint x, y;
+  guint8 *data = apx->data;
+  guint8 *block = apx->block;
+
+  if (!enc->q2available) {
+    enc->q2error =
+        mve_quantize (enc, src, 8, 8, 0, 2, enc->q2block, enc->q2colors);
+    enc->q2available = TRUE;
+  }
+
+  memcpy (block, enc->q2block, 64);
+
+  /* p0 <= p1 */
+  data[0] = MIN (enc->q2colors[0], enc->q2colors[1]);
+  data[1] = MAX (enc->q2colors[0], enc->q2colors[1]);
+  data += 2;
+
+  for (y = 0; y < 8; ++y) {
+    guint8 flags = 0;
+
+    for (x = 0x01; x <= 0x80; x <<= 1) {
+      if (*block == apx->data[1])
+        flags |= x;
+      ++block;
+    }
+    *data++ = flags;
+  }
+
+  apx->error = enc->q2error;
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x8a (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 2-color encoding for top and bottom half (12 bytes) */
+  guint8 cols[2];
+  guint32 flags;
+  guint i, x, y, shifter;
+  guint8 *block = apx->block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 2; ++i) {
+    apx->error += mve_quantize (enc, src, 8, 4, i, 2, apx->block, cols);
+
+    flags = 0;
+    shifter = 0;
+
+    /* p0 > p1 && p2 > p3 */
+    data[0] = MAX (cols[0], cols[1]);
+    data[1] = MIN (cols[0], cols[1]);
+
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 8; ++x, ++shifter) {
+        if (block[x] == data[1])
+          flags |= 1 << shifter;
+      }
+      block += 8;
+    }
+    data[2] = flags & 0x000000FF;
+    data[3] = (flags & 0x0000FF00) >> 8;
+    data[4] = (flags & 0x00FF0000) >> 16;
+    data[5] = (flags & 0xFF000000) >> 24;
+    data += 6;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x8b (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 2-color encoding for left and right half (12 bytes) */
+  guint8 cols[2];
+  guint32 flags;
+  guint i, x, y, shifter;
+  guint8 *block = apx->block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 2; ++i) {
+    apx->error += mve_quantize (enc, src, 4, 8, i, 2, apx->block, cols);
+
+    flags = 0;
+    shifter = 0;
+
+    /* p0 > p1 && p2 <= p3 */
+    data[i] = MAX (cols[0], cols[1]);
+    data[i ^ 1] = MIN (cols[0], cols[1]);
+
+    for (y = 0; y < 8; ++y) {
+      for (x = 0; x < 4; ++x, ++shifter) {
+        if (block[x] == data[1])
+          flags |= 1 << shifter;
+      }
+      block += 8;
+    }
+
+    data[2] = flags & 0x000000FF;
+    data[3] = (flags & 0x0000FF00) >> 8;
+    data[4] = (flags & 0x00FF0000) >> 16;
+    data[5] = (flags & 0xFF000000) >> 24;
+    data += 6;
+    block = apx->block + 4;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x8c (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 2-color encoding for each 4x4 quadrant (16 bytes) */
+  guint8 cols[2];
+  guint16 flags;
+  guint i, x, y, shifter;
+  guint8 *block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 4; ++i) {
+    apx->error +=
+        mve_quantize (enc, src, 4, 4, ((i & 1) << 1) | ((i & 2) >> 1), 2,
+        apx->block, cols);
+
+    /* p0 < p1 */
+    if (i == 0) {
+      data[0] = MIN (cols[0], cols[1]);
+      data[1] = MAX (cols[0], cols[1]);
+    } else {
+      data[0] = cols[0];
+      data[1] = cols[1];
+    }
+
+    block = apx->block + ((i / 2) * 4) + ((i % 2) * 32);
+    flags = 0;
+    shifter = 0;
+
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 4; ++x, ++shifter) {
+        if (block[x] == data[1])
+          flags |= 1 << shifter;
+      }
+      block += 8;
+    }
+
+    data[2] = flags & 0x00FF;
+    data[3] = (flags & 0xFF00) >> 8;
+    data += 4;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x9a (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for 2x2 solid blocks (8 bytes) */
+  guint32 p[4];
+  guint32 e, emin;
+  guint i, x, y, mean = 0;
+  guint8 r[4], g[4], b[4], rb, gb, bb;
+  guint8 *block = apx->block;
+  guint shifter = 0;
+  guint32 flags = 0;
+
+  /* calculate mean colors for the entire block */
+  if (!enc->q4available) {
+    enc->q4error =
+        mve_quantize (enc, src, 8, 8, 0, 4, enc->q4block, enc->q4colors);
+    enc->q4available = TRUE;
+  }
+
+  /* p0 <= p1 && p2 > p3 */
+  apx->data[0] = MIN (enc->q4colors[0], enc->q4colors[1]);
+  apx->data[1] = MAX (enc->q4colors[0], enc->q4colors[1]);
+  apx->data[2] = MAX (enc->q4colors[2], enc->q4colors[3]);
+  apx->data[3] = MIN (enc->q4colors[2], enc->q4colors[3]);
+
+  for (i = 0; i < 4; ++i) {
+    r[i] = MVE_RVAL (enc->palette[apx->data[i]]);
+    g[i] = MVE_GVAL (enc->palette[apx->data[i]]);
+    b[i] = MVE_BVAL (enc->palette[apx->data[i]]);
+  }
+
+  /* calculate mean colors for each 2x2 block and map to global colors */
+  for (y = 0; y < 4; ++y) {
+    for (x = 0; x < 4; ++x, shifter += 2) {
+      p[0] = enc->palette[src[0]];
+      p[1] = enc->palette[src[1]];
+      p[2] = enc->palette[src[enc->mve->width]];
+      p[3] = enc->palette[src[enc->mve->width + 1]];
+
+      rb = (MVE_RVAL (p[0]) + MVE_RVAL (p[1]) + MVE_RVAL (p[2]) +
+          MVE_RVAL (p[3]) + 2) / 4;
+      gb = (MVE_GVAL (p[0]) + MVE_GVAL (p[1]) + MVE_GVAL (p[2]) +
+          MVE_GVAL (p[3]) + 2) / 4;
+      bb = (MVE_BVAL (p[0]) + MVE_BVAL (p[1]) + MVE_BVAL (p[2]) +
+          MVE_BVAL (p[3]) + 2) / 4;
+
+      emin = MVE_APPROX_MAX_ERROR;
+      for (i = 0; i < 4; ++i) {
+        e = mve_color_dist_rgb (rb, gb, bb, r[i], g[i], b[i]);
+        if (e < emin) {
+          emin = e;
+          mean = i;
+        }
+      }
+
+      flags |= mean << shifter;
+      block[0] = block[1] = block[8] = block[9] = apx->data[mean];
+
+      src += 2;
+      block += 2;
+    }
+    src += (enc->mve->width * 2) - 8;
+    block += 8;
+  }
+
+  apx->data[4] = flags & 0x000000FF;
+  apx->data[5] = (flags & 0x0000FF00) >> 8;
+  apx->data[6] = (flags & 0x00FF0000) >> 16;
+  apx->data[7] = (flags & 0xFF000000) >> 24;
+
+  apx->error =
+      mve_block_error_packed (enc, src - 8 * enc->mve->width, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x9b (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for 2x1 solid blocks (12 bytes) */
+  guint32 p[2];
+  guint32 e, emin;
+  guint i, x, y, mean = 0;
+  guint8 r[4], g[4], b[4], rb, gb, bb;
+  guint8 *data = apx->data;
+  guint8 *block = apx->block;
+  guint shifter = 0;
+  guint32 flags = 0;
+
+  /* calculate mean colors for the entire block */
+  if (!enc->q4available) {
+    enc->q4error =
+        mve_quantize (enc, src, 8, 8, 0, 4, enc->q4block, enc->q4colors);
+    enc->q4available = TRUE;
+  }
+
+  /* p0 > p1 && p2 <= p3 */
+  data[0] = MAX (enc->q4colors[0], enc->q4colors[1]);
+  data[1] = MIN (enc->q4colors[0], enc->q4colors[1]);
+  data[2] = MIN (enc->q4colors[2], enc->q4colors[3]);
+  data[3] = MAX (enc->q4colors[2], enc->q4colors[3]);
+
+  for (i = 0; i < 4; ++i) {
+    r[i] = MVE_RVAL (enc->palette[data[i]]);
+    g[i] = MVE_GVAL (enc->palette[data[i]]);
+    b[i] = MVE_BVAL (enc->palette[data[i]]);
+  }
+  data += 4;
+
+  /* calculate mean colors for each 2x1 block and map to global colors */
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 4; ++x, shifter += 2) {
+      p[0] = enc->palette[src[0]];
+      p[1] = enc->palette[src[1]];
+      rb = (MVE_RVAL (p[0]) + MVE_RVAL (p[1]) + 1) / 2;
+      gb = (MVE_GVAL (p[0]) + MVE_GVAL (p[1]) + 1) / 2;
+      bb = (MVE_BVAL (p[0]) + MVE_BVAL (p[1]) + 1) / 2;
+
+      emin = MVE_APPROX_MAX_ERROR;
+      for (i = 0; i < 4; ++i) {
+        e = mve_color_dist_rgb (rb, gb, bb, r[i], g[i], b[i]);
+        if (e < emin) {
+          emin = e;
+          mean = i;
+        }
+      }
+
+      flags |= mean << shifter;
+      block[0] = block[1] = apx->data[mean];
+
+      src += 2;
+      block += 2;
+    }
+
+    if ((y == 3) || (y == 7)) {
+      data[0] = flags & 0x000000FF;
+      data[1] = (flags & 0x0000FF00) >> 8;
+      data[2] = (flags & 0x00FF0000) >> 16;
+      data[3] = (flags & 0xFF000000) >> 24;
+      data += 4;
+
+      flags = 0;
+      shifter = 0;
+    }
+
+    src += enc->mve->width - 8;
+  }
+
+  apx->error =
+      mve_block_error_packed (enc, src - 8 * enc->mve->width, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x9c (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for 1x2 solid blocks (12 bytes) */
+  guint32 p[2];
+  guint32 e, emin;
+  guint i, x, y, mean = 0;
+  guint8 r[4], g[4], b[4], rb, gb, bb;
+  guint8 *data = apx->data;
+  guint8 *block = apx->block;
+  guint shifter = 0;
+  guint32 flags = 0;
+
+  /* calculate mean colors for the entire block */
+  if (!enc->q4available) {
+    enc->q4error =
+        mve_quantize (enc, src, 8, 8, 0, 4, enc->q4block, enc->q4colors);
+    enc->q4available = TRUE;
+  }
+
+  /* p0 > p1 && p2 > p3 */
+  data[0] = MAX (enc->q4colors[0], enc->q4colors[1]);
+  data[1] = MIN (enc->q4colors[0], enc->q4colors[1]);
+  data[2] = MAX (enc->q4colors[2], enc->q4colors[3]);
+  data[3] = MIN (enc->q4colors[2], enc->q4colors[3]);
+
+  for (i = 0; i < 4; ++i) {
+    r[i] = MVE_RVAL (enc->palette[data[i]]);
+    g[i] = MVE_GVAL (enc->palette[data[i]]);
+    b[i] = MVE_BVAL (enc->palette[data[i]]);
+  }
+  data += 4;
+
+  /* calculate mean colors for each 1x2 block and map to global colors */
+  for (y = 0; y < 4; ++y) {
+    for (x = 0; x < 8; ++x, shifter += 2) {
+      p[0] = enc->palette[src[0]];
+      p[1] = enc->palette[src[enc->mve->width]];
+      rb = (MVE_RVAL (p[0]) + MVE_RVAL (p[1]) + 1) / 2;
+      gb = (MVE_GVAL (p[0]) + MVE_GVAL (p[1]) + 1) / 2;
+      bb = (MVE_BVAL (p[0]) + MVE_BVAL (p[1]) + 1) / 2;
+
+      emin = MVE_APPROX_MAX_ERROR;
+      for (i = 0; i < 4; ++i) {
+        e = mve_color_dist_rgb (rb, gb, bb, r[i], g[i], b[i]);
+        if (e < emin) {
+          emin = e;
+          mean = i;
+        }
+      }
+
+      flags |= mean << shifter;
+      block[0] = block[8] = apx->data[mean];
+
+      ++src;
+      ++block;
+    }
+
+    if ((y == 1) || (y == 3)) {
+      data[0] = flags & 0x000000FF;
+      data[1] = (flags & 0x0000FF00) >> 8;
+      data[2] = (flags & 0x00FF0000) >> 16;
+      data[3] = (flags & 0xFF000000) >> 24;
+      data += 4;
+
+      flags = 0;
+      shifter = 0;
+    }
+
+    src += (enc->mve->width * 2) - 8;
+    block += 8;
+  }
+
+  apx->error =
+      mve_block_error_packed (enc, src - 8 * enc->mve->width, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0x9d (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* generic 4-color encoding (20 bytes) */
+  guint32 flags = 0;
+  guint shifter = 0;
+  guint i, x, y;
+  guint8 *data = apx->data;
+  guint8 *block = apx->block;
+
+  if (!enc->q4available) {
+    enc->q4error =
+        mve_quantize (enc, src, 8, 8, 0, 4, enc->q4block, enc->q4colors);
+    enc->q4available = TRUE;
+  }
+
+  memcpy (block, enc->q4block, 64);
+
+  /* p0 <= p1 && p2 <= p3 */
+  data[0] = MIN (enc->q4colors[0], enc->q4colors[1]);
+  data[1] = MAX (enc->q4colors[0], enc->q4colors[1]);
+  data[2] = MIN (enc->q4colors[2], enc->q4colors[3]);
+  data[3] = MAX (enc->q4colors[2], enc->q4colors[3]);
+  data += 4;
+
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; ++x, shifter += 2) {
+
+      for (i = 0; i < 3; ++i) {
+        if (*block == apx->data[i])
+          break;
+      }
+
+      flags |= i << shifter;
+      ++block;
+    }
+
+    data[0] = flags & 0x000000FF;
+    data[1] = (flags & 0x0000FF00) >> 8;
+    data += 2;
+    shifter = 0;
+    flags = 0;
+  }
+
+  apx->error = enc->q4error;
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xaa (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for top and bottom half (24 bytes) */
+  guint8 cols[4];
+  guint32 flags;
+  guint i, j, x, y, shifter;
+  guint8 *block = apx->block;
+  guint8 *data = apx->data;
+  const guint8 *p;
+
+  apx->error = 0;
+
+  for (i = 0; i < 2; ++i) {
+    apx->error += mve_quantize (enc, src, 8, 4, i, 4, apx->block, cols);
+
+    flags = 0;
+    shifter = 0;
+
+    /* p0 > p1 && p4 > p5 */
+    data[0] = MAX (cols[0], cols[1]);
+    data[1] = MIN (cols[0], cols[1]);
+    data[2] = cols[2];
+    data[3] = cols[3];
+    p = data;
+    data += 4;
+
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 8; ++x, shifter += 2) {
+        for (j = 0; j < 3; ++j) {
+          if (block[x] == p[j])
+            break;
+        }
+        flags |= j << shifter;
+      }
+      block += 8;
+
+      if ((y == 1) || (y == 3)) {
+        data[0] = flags & 0x000000FF;
+        data[1] = (flags & 0x0000FF00) >> 8;
+        data[2] = (flags & 0x00FF0000) >> 16;
+        data[3] = (flags & 0xFF000000) >> 24;
+        data += 4;
+        flags = 0;
+        shifter = 0;
+      }
+    }
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xab (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for left and right half (24 bytes) */
+  guint8 cols[4];
+  guint32 flags;
+  guint i, j, x, y, shifter;
+  guint8 *block = apx->block;
+  guint8 *data = apx->data;
+  const guint8 *p;
+
+  apx->error = 0;
+
+  for (i = 0; i < 2; ++i) {
+    apx->error += mve_quantize (enc, src, 4, 8, i, 4, apx->block, cols);
+
+    flags = 0;
+    shifter = 0;
+
+    /* p0 > p1 && p4 <= p5 */
+    data[i] = MAX (cols[0], cols[1]);
+    data[i ^ 1] = MIN (cols[0], cols[1]);
+    data[2] = cols[2];
+    data[3] = cols[3];
+    p = data;
+    data += 4;
+
+    for (y = 0; y < 8; ++y) {
+      for (x = 0; x < 4; ++x, shifter += 2) {
+        for (j = 0; j < 3; ++j) {
+          if (block[x] == p[j])
+            break;
+        }
+        flags |= j << shifter;
+      }
+      block += 8;
+
+      if ((y == 3) || (y == 7)) {
+        data[0] = flags & 0x000000FF;
+        data[1] = (flags & 0x0000FF00) >> 8;
+        data[2] = (flags & 0x00FF0000) >> 16;
+        data[3] = (flags & 0xFF000000) >> 24;
+        data += 4;
+        flags = 0;
+        shifter = 0;
+      }
+    }
+    block = apx->block + 4;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xac (GstMveEncoderData * enc, const guint8 * src,
+    GstMveApprox * apx)
+{
+  /* 4-color encoding for each 4x4 quadrant (32 bytes) */
+  guint8 cols[4];
+  guint32 flags;
+  guint i, j, x, y, shifter;
+  guint8 *block;
+  guint8 *data = apx->data;
+
+  apx->error = 0;
+
+  for (i = 0; i < 4; ++i) {
+    apx->error +=
+        mve_quantize (enc, src, 4, 4, ((i & 1) << 1) | ((i & 2) >> 1), 4,
+        apx->block, cols);
+
+    /* p0 <= p1 */
+    data[0] = MIN (cols[0], cols[1]);
+    data[1] = MAX (cols[0], cols[1]);
+    data[2] = cols[2];
+    data[3] = cols[3];
+
+    block = apx->block + ((i / 2) * 4) + ((i % 2) * 32);
+    flags = 0;
+    shifter = 0;
+
+    for (y = 0; y < 4; ++y) {
+      for (x = 0; x < 4; ++x, shifter += 2) {
+        for (j = 0; j < 3; ++j) {
+          if (block[x] == data[j])
+            break;
+        }
+        flags |= j << shifter;
+      }
+      block += 8;
+    }
+
+    data[4] = flags & 0x000000FF;
+    data[5] = (flags & 0x0000FF00) >> 8;
+    data[6] = (flags & 0x00FF0000) >> 16;
+    data[7] = (flags & 0xFF000000) >> 24;
+    data += 8;
+  }
+
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xb (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* 64-color encoding (each pixel in block is a different color) (64 bytes) */
+  mve_store_block (enc->mve, src, apx->block);
+  memcpy (apx->data, apx->block, 64);
+  apx->error = 0;
+
+  return 0;
+}
+
+static guint32
+mve_encode_0xc (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* 16-color block encoding: each 2x2 block is a different color (16 bytes) */
+  guint i = 0, x, y;
+  const guint w = enc->mve->width;
+  guint16 r, g, b;
+
+  /* calculate median color for each 2x2 block */
+  for (y = 0; y < 4; ++y) {
+    for (x = 0; x < 4; ++x) {
+      guint32 p = enc->palette[src[0]];
+
+      r = MVE_RVAL (p) + 2;
+      g = MVE_GVAL (p) + 2;
+      b = MVE_BVAL (p) + 2;
+
+      p = enc->palette[src[1]];
+      r += MVE_RVAL (p);
+      g += MVE_GVAL (p);
+      b += MVE_BVAL (p);
+
+      p = enc->palette[src[w]];
+      r += MVE_RVAL (p);
+      g += MVE_GVAL (p);
+      b += MVE_BVAL (p);
+
+      p = enc->palette[src[w + 1]];
+      r += MVE_RVAL (p);
+      g += MVE_GVAL (p);
+      b += MVE_BVAL (p);
+
+      apx->block[i] = apx->block[i + 1] = apx->block[i + 2] =
+          apx->block[i + 3] = apx->data[i >> 2] =
+          mve_find_pal_color (enc->palette, MVE_COL (r >> 2, g >> 2, b >> 2));
+
+      i += 4;
+      src += 2;
+    }
+    src += (w * 2) - 8;
+  }
+
+  apx->error = mve_block_error_packed (enc, src - (8 * w), apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xd (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* 4-color block encoding: each 4x4 block is a different color (4 bytes) */
+  guint i, y;
+
+  /* calculate median color for each 4x4 block */
+  for (i = 0; i < 4; ++i) {
+    guint8 median =
+        mve_median_sub (enc, src, 4, 4, ((i & 1) << 1) | ((i & 2) >> 1));
+    guint8 *block = apx->block + ((i / 2) * 4) + ((i % 2) * 32);
+
+    for (y = 0; y < 4; ++y) {
+      memset (block, median, 4);
+      block += 8;
+    }
+
+    apx->data[i] = median;
+  }
+
+  apx->error = mve_block_error_packed (enc, src, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xe (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* 1-color encoding: the whole block is 1 solid color (1 bytes) */
+  guint8 median = mve_median (enc, src);
+
+  memset (apx->block, median, 64);
+
+  apx->data[0] = median;
+  apx->error = mve_block_error_packed (enc, src, apx->block);
+  return apx->error;
+}
+
+static guint32
+mve_encode_0xf (GstMveEncoderData * enc, const guint8 * src, GstMveApprox * apx)
+{
+  /* 2 colors dithered encoding (2 bytes) */
+  guint i, x, y;
+  guint32 r[2] = { 0 }, g[2] = {
+  0}, b[2] = {
+  0};
+  guint8 col[2];
+
+  /* find medians for both colors */
+  for (y = 0; y < 8; ++y) {
+    for (x = 0; x < 8; x += 2) {
+      guint16 p = src[x];
+
+      r[y & 1] += MVE_RVAL (p);
+      g[y & 1] += MVE_GVAL (p);
+      b[y & 1] += MVE_BVAL (p);
+
+      p = src[x + 1];
+      r[(y & 1) ^ 1] += MVE_RVAL (p);
+      g[(y & 1) ^ 1] += MVE_GVAL (p);
+      b[(y & 1) ^ 1] += MVE_BVAL (p);
+    }
+    src += enc->mve->width;
+  }
+  col[0] = mve_find_pal_color (enc->palette,
+      MVE_COL ((r[0] + 16) / 32, (g[0] + 16) / 32, (b[0] + 16) / 32));
+  col[1] = mve_find_pal_color (enc->palette,
+      MVE_COL ((r[1] + 16) / 32, (g[1] + 16) / 32, (b[1] + 16) / 32));
+
+  /* store block after encoding */
+  for (i = 0, y = 0; y < 8; ++y) {
+    for (x = 0; x < 4; ++x) {
+      apx->block[i++] = col[y & 1];
+      apx->block[i++] = col[(y & 1) ^ 1];
+    }
+  }
+
+  apx->data[0] = col[0];
+  apx->data[1] = col[1];
+  apx->error = mve_block_error_packed (enc,
+      src - (8 * enc->mve->width), apx->block);
+  return apx->error;
+}
+
+/* all available encodings in the preferred order,
+   i.e. in ascending encoded size */
+static const GstMveEncoding mve_encodings[] = {
+  {0x1, 0, mve_encode_0x1},
+  {0x0, 0, mve_encode_0x0},
+  {0xe, 1, mve_encode_0xe},
+  {0x3, 1, mve_encode_0x3},
+  {0x4, 1, mve_encode_0x4},
+  {0x2, 1, mve_encode_0x2},
+  {0xf, 2, mve_encode_0xf},
+  {0x5, 2, mve_encode_0x5},
+  {0xd, 4, mve_encode_0xd},
+  {0x7, 4, mve_encode_0x7a},
+  {0x9, 8, mve_encode_0x9a},
+  {0x7, 10, mve_encode_0x7b},
+  {0x8, 12, mve_encode_0x8a},
+  {0x8, 12, mve_encode_0x8b},
+  {0x9, 12, mve_encode_0x9b},
+  {0x9, 12, mve_encode_0x9c},
+  {0xc, 16, mve_encode_0xc},
+  {0x8, 16, mve_encode_0x8c},
+  {0x9, 20, mve_encode_0x9d},
+  {0xa, 24, mve_encode_0xaa},
+  {0xa, 24, mve_encode_0xab},
+  {0xa, 32, mve_encode_0xac},
+  {0xb, 64, mve_encode_0xb}
+};
+
+static gboolean
+mve_reorder_solution (GArray ** solution, guint16 n)
+{
+  /* do a binary search to find the position to reinsert the modified element */
+  /* the block we need to reconsider is always at position 0 */
+  /* return TRUE if this block only has 1 encoding left and can be dropped */
+  if (mve_comp_solution (&solution[0], &solution[1]) <= 0)
+    return FALSE;               /* already sorted */
+
+  else if (solution[0]->len <= 1)
+    /* drop this element from further calculations since we cannot improve here */
+    return TRUE;
+
+  else {
+    /* we know the error value can only get worse, so we can actually start at 1 */
+    guint lower = 1;
+    guint upper = n - 1;
+    gint cmp;
+    guint idx = 0;
+
+    while (upper > lower) {
+      idx = lower + ((upper - lower) / 2);
+
+      cmp = mve_comp_solution (&solution[0], &solution[idx]);
+
+      if (cmp < 0) {
+        upper = idx;
+      } else if (cmp > 0) {
+        lower = ++idx;
+      } else {
+        upper = lower = idx;
+      }
+    }
+
+    if (idx > 0) {
+      /* rearrange array members in new order */
+      GArray *a = solution[0];
+
+      memcpy (&solution[0], &solution[1], sizeof (GArray *) * idx);
+      solution[idx] = a;
+    }
+  }
+  return FALSE;
+}
+
+static guint32
+gst_mve_find_solution (GArray ** approx, guint16 n, guint32 size, guint16 max)
+{
+  /* build an array of approximations we can shuffle around */
+  GstMveApprox *sol_apx;
+  GArray **solution = g_malloc (sizeof (GArray *) * n);
+  GArray **current = solution;
+
+  memcpy (solution, approx, sizeof (GArray *) * n);
+
+  qsort (solution, n, sizeof (GArray *), mve_comp_solution);
+
+  do {
+    /* array is now sorted by error of the next to optimal approximation;
+       drop optimal approximation for the best block */
+
+    /* unable to reduce size further */
+    if (current[0]->len <= 1)
+      break;
+
+    sol_apx = &g_array_index (current[0], GstMveApprox, current[0]->len - 1);
+    size -= mve_encodings[sol_apx->type].size;
+    g_array_remove_index_fast (current[0], current[0]->len - 1);
+    sol_apx = &g_array_index (current[0], GstMveApprox, current[0]->len - 1);
+    size += mve_encodings[sol_apx->type].size;
+
+    if (mve_reorder_solution (current, n)) {
+      ++current;
+      --n;
+    }
+  } while (size > max);
+
+  g_free (solution);
+
+  return size;
+}
+
+GstFlowReturn
+mve_encode_frame8 (GstMveMux * mve, GstBuffer * frame, const guint32 * palette,
+    guint16 max_data)
+{
+  guint8 *src;
+  GstFlowReturn ret = GST_FLOW_ERROR;
+  guint8 *cm = mve->chunk_code_map;
+  GArray **approx;
+  GstMveApprox apx;
+  GstMveEncoderData enc;
+  const guint16 blocks = (mve->width * mve->height) / 64;
+  guint32 encoded_size = 0;
+  guint i = 0, x, y;
+
+  src = GST_BUFFER_DATA (frame);
+
+  approx = g_malloc (sizeof (GArray *) * blocks);
+
+  enc.mve = mve;
+  enc.palette = palette;
+
+  for (enc.y = 0; enc.y < mve->height; enc.y += 8) {
+    for (enc.x = 0; enc.x < mve->width; enc.x += 8) {
+      guint32 err, last_err = MVE_APPROX_MAX_ERROR;
+      guint type = 0;
+      guint best = 0;
+
+      enc.q2available = enc.q4available = FALSE;
+      approx[i] = g_array_new (FALSE, FALSE, sizeof (GstMveApprox));
+
+      do {
+        err = mve_encodings[type].approx (&enc, src, &apx);
+
+        if (err < last_err) {
+          apx.type = best = type;
+          g_array_append_val (approx[i], apx);
+          last_err = err;
+        }
+
+        ++type;
+      } while (last_err != 0);
+
+      encoded_size += mve_encodings[best].size;
+      ++i;
+      src += 8;
+    }
+    src += 7 * mve->width;
+  }
+
+  /* find best solution with size constraints */
+  GST_DEBUG_OBJECT (mve, "encoded frame %u in %ld bytes (lossless)",
+      mve->video_frames + 1, encoded_size);
+
+#if 0
+  /* FIXME */
+  src = GST_BUFFER_DATA (frame);
+  for (i = 0, y = 0; y < mve->height; y += 8) {
+    for (x = 0; x < mve->width; x += 8, ++i) {
+      GstMveApprox *sol =
+          &g_array_index (approx[i], GstMveApprox, approx[i]->len - 1);
+      guint opcode = mve_encodings[sol->type].opcode;
+      guint j, k;
+
+      if (sol->error > 0)
+        GST_WARNING_OBJECT (mve, "error is %lu for %d/%d (0x%x)", sol->error, x,
+            y, opcode);
+
+      for (j = 0; j < 8; ++j) {
+        guint8 *o = src + j * mve->width;
+        guint8 *c = sol->block + j * 8;
+
+        if (memcmp (o, c, 8)) {
+          GST_WARNING_OBJECT (mve, "opcode 0x%x (type %d) at %d/%d, line %d:",
+              opcode, sol->type, x, y, j + 1);
+          for (k = 0; k < 8; ++k) {
+            o = src + k * mve->width;
+            c = sol->block + k * 8;
+            GST_WARNING_OBJECT (mve,
+                "%d should be: %4d  %4d  %4d  %4d  %4d  %4d  %4d  %4d", k, o[0],
+                o[1], o[2], o[3], o[4], o[5], o[6], o[7]);
+            GST_WARNING_OBJECT (mve,
+                "%d but is   : %4d  %4d  %4d  %4d  %4d  %4d  %4d  %4d", k, c[0],
+                c[1], c[2], c[3], c[4], c[5], c[6], c[7]);
+          }
+        }
+      }
+      src += 8;
+    }
+    src += 7 * mve->width;
+  }
+#endif
+
+  if (encoded_size > max_data) {
+    encoded_size =
+        gst_mve_find_solution (approx, blocks, encoded_size, max_data);
+    if (encoded_size > max_data) {
+      GST_ERROR_OBJECT (mve, "unable to compress frame to less than %d bytes",
+          encoded_size);
+      for (i = 0; i < blocks; ++i)
+        g_array_free (approx[i], TRUE);
+
+      goto done;
+    }
+    GST_DEBUG_OBJECT (mve, "compressed frame %u to %ld bytes (lossy)",
+        mve->video_frames + 1, encoded_size);
+  }
+
+  mve->chunk_video = g_byte_array_sized_new (encoded_size);
+
+  /* encode */
+  src = GST_BUFFER_DATA (frame);
+  for (i = 0, y = 0; y < mve->height; y += 8) {
+    for (x = 0; x < mve->width; x += 8, ++i) {
+      GstMveApprox *sol =
+          &g_array_index (approx[i], GstMveApprox, approx[i]->len - 1);
+      guint opcode = mve_encodings[sol->type].opcode;
+
+      g_byte_array_append (mve->chunk_video, sol->data,
+          mve_encodings[sol->type].size);
+
+      if (i & 1) {
+        *cm |= opcode << 4;
+        ++cm;
+      } else
+        *cm = opcode;
+
+      /* modify the frame to match the image we actually encoded */
+      if (sol->error > 0)
+        mve_restore_block (mve, src, sol->block);
+
+      src += 8;
+      g_array_free (approx[i], TRUE);
+    }
+    src += 7 * mve->width;
+  }
+
+  ret = GST_FLOW_OK;
+
+done:
+  g_free (approx);
+
+  return ret;
+}