diff options
author | Jens Granseuer <jensgr@gmx.net> | 2007-01-11 11:39:56 +0000 |
---|---|---|
committer | Tim-Philipp Müller <tim@centricular.net> | 2007-01-11 11:39:56 +0000 |
commit | 5dbec4ecf422943e0a9b7dadb16106b29e0753ca (patch) | |
tree | a874c5d91bc2227b7880dc5e5db337e7669c2b81 /gst | |
parent | abe3e58b1b29867f0728cf0d108602833287e0cf (diff) | |
download | gst-plugins-bad-5dbec4ecf422943e0a9b7dadb16106b29e0753ca.tar.gz gst-plugins-bad-5dbec4ecf422943e0a9b7dadb16106b29e0753ca.tar.bz2 gst-plugins-bad-5dbec4ecf422943e0a9b7dadb16106b29e0753ca.zip |
Add Interplay MVE format demuxer/decoder and muxer/encoder. Demuxer doesn't support seeking yet, but seems to work fi...
Original commit message from CVS:
Patch by: Jens Granseuer <jensgr at gmx net>
* configure.ac:
* gst/mve/Makefile.am:
* gst/mve/TODO:
* gst/mve/gstmve.c:
* gst/mve/gstmvedemux.c:
* gst/mve/gstmvedemux.h:
* gst/mve/gstmvemux.c:
* gst/mve/gstmvemux.h:
* gst/mve/mve.h:
* gst/mve/mveaudiodec.c:
* gst/mve/mveaudioenc.c:
* gst/mve/mvevideodec16.c:
* gst/mve/mvevideodec8.c:
* gst/mve/mvevideoenc16.c:
* gst/mve/mvevideoenc8.c:
Add Interplay MVE format demuxer/decoder and muxer/encoder. Demuxer
doesn't support seeking yet, but seems to work fine otherwise.
Closes #348973.
Diffstat (limited to 'gst')
-rw-r--r-- | gst/mve/Makefile.am | 20 | ||||
-rw-r--r-- | gst/mve/TODO | 5 | ||||
-rw-r--r-- | gst/mve/gstmve.c | 46 | ||||
-rw-r--r-- | gst/mve/gstmvedemux.c | 1126 | ||||
-rw-r--r-- | gst/mve/gstmvedemux.h | 104 | ||||
-rw-r--r-- | gst/mve/gstmvemux.c | 1493 | ||||
-rw-r--r-- | gst/mve/gstmvemux.h | 120 | ||||
-rw-r--r-- | gst/mve/mve.h | 62 | ||||
-rw-r--r-- | gst/mve/mveaudiodec.c | 82 | ||||
-rw-r--r-- | gst/mve/mveaudioenc.c | 120 | ||||
-rw-r--r-- | gst/mve/mvevideodec16.c | 849 | ||||
-rw-r--r-- | gst/mve/mvevideodec8.c | 802 | ||||
-rw-r--r-- | gst/mve/mvevideoenc16.c | 1649 | ||||
-rw-r--r-- | gst/mve/mvevideoenc8.c | 1733 |
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; +} |