summaryrefslogtreecommitdiffstats
path: root/gst/mve/gstmvedemux.c
diff options
context:
space:
mode:
authorJens Granseuer <jensgr@gmx.net>2007-01-11 11:39:56 +0000
committerTim-Philipp Müller <tim@centricular.net>2007-01-11 11:39:56 +0000
commit5dbec4ecf422943e0a9b7dadb16106b29e0753ca (patch)
treea874c5d91bc2227b7880dc5e5db337e7669c2b81 /gst/mve/gstmvedemux.c
parentabe3e58b1b29867f0728cf0d108602833287e0cf (diff)
downloadgst-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/mve/gstmvedemux.c')
-rw-r--r--gst/mve/gstmvedemux.c1126
1 files changed, 1126 insertions, 0 deletions
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;
+}