summaryrefslogtreecommitdiffstats
path: root/gst/mve
diff options
context:
space:
mode:
Diffstat (limited to 'gst/mve')
-rw-r--r--gst/mve/Makefile.am20
-rw-r--r--gst/mve/TODO5
-rw-r--r--gst/mve/gstmve.c46
-rw-r--r--gst/mve/gstmvedemux.c1126
-rw-r--r--gst/mve/gstmvedemux.h104
-rw-r--r--gst/mve/gstmvemux.c1493
-rw-r--r--gst/mve/gstmvemux.h120
-rw-r--r--gst/mve/mve.h62
-rw-r--r--gst/mve/mveaudiodec.c82
-rw-r--r--gst/mve/mveaudioenc.c120
-rw-r--r--gst/mve/mvevideodec16.c849
-rw-r--r--gst/mve/mvevideodec8.c802
-rw-r--r--gst/mve/mvevideoenc16.c1649
-rw-r--r--gst/mve/mvevideoenc8.c1733
14 files changed, 8211 insertions, 0 deletions
diff --git a/gst/mve/Makefile.am b/gst/mve/Makefile.am
new file mode 100644
index 00000000..3b1a5a4c
--- /dev/null
+++ b/gst/mve/Makefile.am
@@ -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
index 00000000..4384f3e9
--- /dev/null
+++ b/gst/mve/TODO
@@ -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
index 00000000..ae1014b2
--- /dev/null
+++ b/gst/mve/gstmve.c
@@ -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
index 00000000..767e730d
--- /dev/null
+++ b/gst/mve/gstmvedemux.c
@@ -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
index 00000000..533d0230
--- /dev/null
+++ b/gst/mve/gstmvedemux.h
@@ -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
index 00000000..d66833f1
--- /dev/null
+++ b/gst/mve/gstmvemux.c
@@ -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
index 00000000..319b2ae4
--- /dev/null
+++ b/gst/mve/gstmvemux.h
@@ -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
index 00000000..782289e4
--- /dev/null
+++ b/gst/mve/mve.h
@@ -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
index 00000000..0e9ee4e2
--- /dev/null
+++ b/gst/mve/mveaudiodec.c
@@ -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
index 00000000..1de73753
--- /dev/null
+++ b/gst/mve/mveaudioenc.c
@@ -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] =
+ { 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, 35563, 38808, 42350, 46214, 50431, 55033, 60055,
+ 65535, 1, -65535, -60055, -55033, -50431, -46214, -42350, -38808, -35563,
+ -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
+};
+
+
+/* This value could be non-optimal. Without knowledge of the value
+ distribution in the real signal, the actual optimum cannot be evaluated.
+ Should be somewhere between 11.458 and 11.542. */
+static const gdouble DPCM_SCALE = 11.5131;
+ static 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];
+ if (delta >= 0)
+ v = mve_enc_delta (delta);
+
+ else
+ v = 256 - mve_enc_delta (-delta);
+ real_res = dec_table[v] + prev[cur_chan];
+ if (real_res < -32768 || real_res > 32767) {
+
+ /* correct overflow */
+ /* 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) {
+ if (real_res > 32767)
+ --v;
+ } else {
+ if (real_res < -32768)
+ ++v;
+ }
+
+ real_res = dec_table[v] + prev[cur_chan];
+ }
+
+ if (G_UNLIKELY (abs (real_res - s) > 32767)) {
+ GST_ERROR ("sign loss left unfixed in audio stream, deviation:%ld",
+ real_res - s);
+ return -1;
+ }
+ *dest++ = v;
+ --len;
+ /* use previous output instead of input. That way output will not go too far from input. */
+ prev[cur_chan] += dec_table[v];
+ cur_chan = channels - 1 - cur_chan;
+ }
+
+ return 0;
+}
diff --git a/gst/mve/mvevideodec16.c b/gst/mve/mvevideodec16.c
new file mode 100644
index 00000000..8a46a59f
--- /dev/null
+++ b/gst/mve/mvevideodec16.c
@@ -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
index 00000000..73e9172f
--- /dev/null
+++ b/gst/mve/mvevideodec8.c
@@ -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
index 00000000..4a18389b
--- /dev/null
+++ b/gst/mve/mvevideoenc16.c
@@ -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
index 00000000..78e3eb17
--- /dev/null
+++ b/gst/mve/mvevideoenc8.c
@@ -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;
+}