blob: fced0564a256b91134b44be6d347763fe83a2ee8 [file] [log] [blame]
/* GStreamer demultiplexer plugin for Interplay MVE movie files
*
* Copyright (C) 2006-2008 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., 51 Franklin St, 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
*/
#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
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,
GST_QUERY_SEEKING,
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;
}
case GST_QUERY_SEEKING:{
GstFormat format;
gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
if (format == GST_FORMAT_TIME) {
gst_query_set_seeking (query, GST_FORMAT_TIME, FALSE, 0, -1);
res = TRUE;
}
break;
}
case GST_QUERY_DURATION:{
/* FIXME: really should implement/estimate this somehow */
res = FALSE;
break;
}
default:
res = gst_pad_query_default (pad, query);
break;
}
return res;
}
static gboolean
gst_mve_demux_handle_src_event (GstPad * pad, GstEvent * event)
{
gboolean res;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
GST_DEBUG ("seeking not supported");
res = FALSE;
break;
default:
res = gst_pad_event_default (pad, event);
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_event_function (stream->pad,
GST_DEBUG_FUNCPTR (gst_mve_demux_handle_src_event));
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)
{
*buffer = gst_buffer_new_and_alloc (size);
gst_buffer_set_caps (*buffer, stream->caps);
GST_BUFFER_TIMESTAMP (*buffer) = stream->last_ts;
GST_BUFFER_OFFSET (*buffer) = stream->offset;
return GST_FLOW_OK;
}
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;
stream->last_flow = GST_FLOW_OK;
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:%u, h:%u, n:%u, true_color:%u", 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:%u, count:%u", 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:%u", 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;
stream->last_flow = GST_FLOW_OK;
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:%d, channels:%d, "
"bits_per_sample:%d, compression:%d, buffer:%u",
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 : G_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)) ? GST_FLOW_OK : GST_FLOW_ERROR;
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 == MVE_OC_AUDIO_DATA) {
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_DEBUG_OBJECT (mve, "created audio buffer, size:%u, stream_mask:%x",
size, stream_mask);
} else {
/* silence - create a minimal buffer with no sound */
size = s->n_channels * (s->sample_size / 8);
ret = gst_mve_buffer_alloc_for_pad (s, size, &buf);
memset (GST_BUFFER_DATA (buf), 0, size);
}
GST_BUFFER_DURATION (buf) = duration;
GST_BUFFER_OFFSET_END (buf) = s->offset + n_samples;
*output = buf;
s->offset += n_samples;
s->last_ts += duration;
} else {
/* alternate audio streams not supported.
are there any movies which use them? */
if (type == MVE_OC_AUDIO_DATA)
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:%ux%u", 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)) ? GST_FLOW_OK : GST_FLOW_ERROR;
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:%d, available:%u",
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
" (%u 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);
stream->last_flow = ret;
}
if (ret == GST_FLOW_NOT_LINKED) {
if (mve->audio_stream
&& mve->audio_stream->last_flow != GST_FLOW_NOT_LINKED)
ret = GST_FLOW_OK;
if (mve->video_stream
&& mve->video_stream->last_flow != GST_FLOW_NOT_LINKED)
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)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
gst_element_class_add_static_pad_template (element_class, &sink_template);
gst_element_class_add_static_pad_template (element_class, &vidsrc_template);
gst_element_class_add_static_pad_template (element_class, &audsrc_template);
gst_element_class_set_static_metadata (element_class, "MVE Demuxer",
"Codec/Demuxer",
"Demultiplex an Interplay movie (MVE) stream into audio and video",
"Jens Granseuer <jensgr@gmx.net>");
}
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) {
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;
}