mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-22 08:17:01 +00:00
153a8ae752
Solves overreading/writing the given arrays and will error out if the streams asks to do that. Also does more error checking that the stream is valid and won't overrun any allocated arrays. Also mitigate integer overflow errors calculating allocation sizes. https://bugzilla.gnome.org/show_bug.cgi?id=774859
981 lines
27 KiB
C
981 lines
27 KiB
C
/* GStreamer
|
|
* Copyright (C) <1999> Erik Walthinsen <omega@temple-baptist.com>
|
|
* Copyright (C) <2016> Matthew Waters <matthew@centricular.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
/**
|
|
* SECTION:element-flxdec
|
|
*
|
|
* This element decodes fli/flc/flx-video into raw video
|
|
*/
|
|
/*
|
|
* http://www.coolutils.com/Formats/FLI
|
|
* http://woodshole.er.usgs.gov/operations/modeling/flc.html
|
|
* http://www.compuphase.com/flic.htm
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
#include <string.h>
|
|
|
|
#include "flx_fmt.h"
|
|
#include "gstflxdec.h"
|
|
#include <gst/video/video.h>
|
|
|
|
#define JIFFIE (GST_SECOND/70)
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (flxdec_debug);
|
|
#define GST_CAT_DEFAULT flxdec_debug
|
|
|
|
/* input */
|
|
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-fli")
|
|
);
|
|
|
|
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
|
#define RGB_ORDER "xRGB"
|
|
#else
|
|
#define RGB_ORDER "BGRx"
|
|
#endif
|
|
|
|
/* output */
|
|
static GstStaticPadTemplate src_video_factory = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (RGB_ORDER))
|
|
);
|
|
|
|
static void gst_flxdec_dispose (GstFlxDec * flxdec);
|
|
|
|
static GstFlowReturn gst_flxdec_chain (GstPad * pad, GstObject * parent,
|
|
GstBuffer * buf);
|
|
static gboolean gst_flxdec_sink_event_handler (GstPad * pad,
|
|
GstObject * parent, GstEvent * event);
|
|
|
|
static GstStateChangeReturn gst_flxdec_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
|
|
static gboolean gst_flxdec_src_query_handler (GstPad * pad, GstObject * parent,
|
|
GstQuery * query);
|
|
|
|
static gboolean flx_decode_color (GstFlxDec * flxdec, GstByteReader * reader,
|
|
GstByteWriter * writer, gint scale);
|
|
static gboolean flx_decode_brun (GstFlxDec * flxdec,
|
|
GstByteReader * reader, GstByteWriter * writer);
|
|
static gboolean flx_decode_delta_fli (GstFlxDec * flxdec,
|
|
GstByteReader * reader, GstByteWriter * writer);
|
|
static gboolean flx_decode_delta_flc (GstFlxDec * flxdec,
|
|
GstByteReader * reader, GstByteWriter * writer);
|
|
|
|
#define rndalign(off) ((off) + ((off) & 1))
|
|
|
|
#define gst_flxdec_parent_class parent_class
|
|
G_DEFINE_TYPE (GstFlxDec, gst_flxdec, GST_TYPE_ELEMENT);
|
|
|
|
static void
|
|
gst_flxdec_class_init (GstFlxDecClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
gobject_class->dispose = (GObjectFinalizeFunc) gst_flxdec_dispose;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (flxdec_debug, "flxdec", 0, "FLX video decoder");
|
|
|
|
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_flxdec_change_state);
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class, "FLX video decoder",
|
|
"Codec/Decoder/Video",
|
|
"FLC/FLI/FLX video decoder",
|
|
"Sepp Wijnands <mrrazz@garbage-coderz.net>, Zeeshan Ali <zeenix@gmail.com>");
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&sink_factory));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&src_video_factory));
|
|
}
|
|
|
|
static void
|
|
gst_flxdec_init (GstFlxDec * flxdec)
|
|
{
|
|
flxdec->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
|
|
gst_element_add_pad (GST_ELEMENT (flxdec), flxdec->sinkpad);
|
|
gst_pad_set_chain_function (flxdec->sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_flxdec_chain));
|
|
gst_pad_set_event_function (flxdec->sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_flxdec_sink_event_handler));
|
|
|
|
flxdec->srcpad = gst_pad_new_from_static_template (&src_video_factory, "src");
|
|
gst_element_add_pad (GST_ELEMENT (flxdec), flxdec->srcpad);
|
|
gst_pad_set_query_function (flxdec->srcpad,
|
|
GST_DEBUG_FUNCPTR (gst_flxdec_src_query_handler));
|
|
|
|
gst_pad_use_fixed_caps (flxdec->srcpad);
|
|
|
|
flxdec->adapter = gst_adapter_new ();
|
|
}
|
|
|
|
static void
|
|
gst_flxdec_dispose (GstFlxDec * flxdec)
|
|
{
|
|
if (flxdec->adapter) {
|
|
g_object_unref (flxdec->adapter);
|
|
flxdec->adapter = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose ((GObject *) flxdec);
|
|
}
|
|
|
|
static gboolean
|
|
gst_flxdec_src_query_handler (GstPad * pad, GstObject * parent,
|
|
GstQuery * query)
|
|
{
|
|
GstFlxDec *flxdec = (GstFlxDec *) parent;
|
|
gboolean ret = FALSE;
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_DURATION:
|
|
{
|
|
GstFormat format;
|
|
|
|
gst_query_parse_duration (query, &format, NULL);
|
|
|
|
if (format != GST_FORMAT_TIME)
|
|
goto done;
|
|
|
|
gst_query_set_duration (query, format, flxdec->duration);
|
|
|
|
ret = TRUE;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
done:
|
|
if (!ret)
|
|
ret = gst_pad_query_default (pad, parent, query);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_flxdec_sink_event_handler (GstPad * pad, GstObject * parent,
|
|
GstEvent * event)
|
|
{
|
|
GstFlxDec *flxdec;
|
|
gboolean ret;
|
|
|
|
flxdec = GST_FLXDEC (parent);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEGMENT:
|
|
{
|
|
GstSegment segment;
|
|
|
|
gst_event_copy_segment (event, &segment);
|
|
if (segment.format != GST_FORMAT_TIME) {
|
|
GST_DEBUG_OBJECT (flxdec, "generating TIME segment");
|
|
gst_segment_init (&segment, GST_FORMAT_TIME);
|
|
gst_event_unref (event);
|
|
event = gst_event_new_segment (&segment);
|
|
}
|
|
/* fall-through */
|
|
}
|
|
default:
|
|
ret = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
flx_decode_chunks (GstFlxDec * flxdec, gulong n_chunks, GstByteReader * reader,
|
|
GstByteWriter * writer)
|
|
{
|
|
gboolean ret = TRUE;
|
|
|
|
while (n_chunks--) {
|
|
GstByteReader chunk;
|
|
guint32 size;
|
|
guint16 type;
|
|
|
|
if (!gst_byte_reader_get_uint32_le (reader, &size))
|
|
goto parse_error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &type))
|
|
goto parse_error;
|
|
GST_LOG_OBJECT (flxdec, "chunk has type 0x%02x size %d", type, size);
|
|
|
|
if (!gst_byte_reader_get_sub_reader (reader, &chunk,
|
|
size - FlxFrameChunkSize)) {
|
|
GST_ERROR_OBJECT (flxdec, "Incorrect size in the chunk header");
|
|
goto error;
|
|
}
|
|
|
|
switch (type) {
|
|
case FLX_COLOR64:
|
|
ret = flx_decode_color (flxdec, &chunk, writer, 2);
|
|
break;
|
|
|
|
case FLX_COLOR256:
|
|
ret = flx_decode_color (flxdec, &chunk, writer, 0);
|
|
break;
|
|
|
|
case FLX_BRUN:
|
|
ret = flx_decode_brun (flxdec, &chunk, writer);
|
|
break;
|
|
|
|
case FLX_LC:
|
|
ret = flx_decode_delta_fli (flxdec, &chunk, writer);
|
|
break;
|
|
|
|
case FLX_SS2:
|
|
ret = flx_decode_delta_flc (flxdec, &chunk, writer);
|
|
break;
|
|
|
|
case FLX_BLACK:
|
|
ret = gst_byte_writer_fill (writer, 0, flxdec->size);
|
|
break;
|
|
|
|
case FLX_MINI:
|
|
break;
|
|
|
|
default:
|
|
GST_WARNING ("Unimplemented chunk type: 0x%02x size: %d - skipping",
|
|
type, size);
|
|
break;
|
|
}
|
|
|
|
if (!ret)
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
|
|
parse_error:
|
|
GST_ERROR_OBJECT (flxdec, "Failed to decode chunk");
|
|
error:
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
flx_decode_color (GstFlxDec * flxdec, GstByteReader * reader,
|
|
GstByteWriter * writer, gint scale)
|
|
{
|
|
guint8 count, indx;
|
|
guint16 packs;
|
|
|
|
if (!gst_byte_reader_get_uint16_le (reader, &packs))
|
|
goto error;
|
|
indx = 0;
|
|
|
|
GST_LOG ("GstFlxDec: cmap packs: %d", (guint) packs);
|
|
while (packs--) {
|
|
const guint8 *data;
|
|
guint16 actual_count;
|
|
|
|
/* color map index + skip count */
|
|
if (!gst_byte_reader_get_uint8 (reader, &indx))
|
|
goto error;
|
|
|
|
/* number of rgb triplets */
|
|
if (!gst_byte_reader_get_uint8 (reader, &count))
|
|
goto error;
|
|
|
|
actual_count = count == 0 ? 256 : count;
|
|
|
|
if (!gst_byte_reader_get_data (reader, count * 3, &data))
|
|
goto error;
|
|
|
|
GST_LOG_OBJECT (flxdec, "cmap count: %d (indx: %d)", actual_count, indx);
|
|
flx_set_palette_vector (flxdec->converter, indx, actual_count,
|
|
(guchar *) data, scale);
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (flxdec, "Error decoding color palette");
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
flx_decode_brun (GstFlxDec * flxdec, GstByteReader * reader,
|
|
GstByteWriter * writer)
|
|
{
|
|
gulong lines, row;
|
|
|
|
g_return_val_if_fail (flxdec != NULL, FALSE);
|
|
|
|
lines = flxdec->hdr.height;
|
|
while (lines--) {
|
|
/* packet count.
|
|
* should not be used anymore, since the flc format can
|
|
* contain more then 255 RLE packets. we use the frame
|
|
* width instead.
|
|
*/
|
|
if (!gst_byte_reader_skip (reader, 1))
|
|
goto error;
|
|
|
|
row = flxdec->hdr.width;
|
|
while (row) {
|
|
gint8 count;
|
|
|
|
if (!gst_byte_reader_get_int8 (reader, &count))
|
|
goto error;
|
|
|
|
if (count <= 0) {
|
|
const guint8 *data;
|
|
|
|
/* literal run */
|
|
count = ABS (count);
|
|
|
|
GST_LOG_OBJECT (flxdec, "have literal run of size %d", count);
|
|
|
|
if (count > row) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid BRUN line detected. "
|
|
"bytes to write exceeds the end of the row");
|
|
return FALSE;
|
|
}
|
|
row -= count;
|
|
|
|
if (!gst_byte_reader_get_data (reader, count, &data))
|
|
goto error;
|
|
if (!gst_byte_writer_put_data (writer, data, count))
|
|
goto error;
|
|
} else {
|
|
guint8 x;
|
|
|
|
GST_LOG_OBJECT (flxdec, "have replicate run of size %d", count);
|
|
|
|
if (count > row) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid BRUN packet detected."
|
|
"bytes to write exceeds the end of the row");
|
|
return FALSE;
|
|
}
|
|
|
|
/* replicate run */
|
|
row -= count;
|
|
|
|
if (!gst_byte_reader_get_uint8 (reader, &x))
|
|
goto error;
|
|
if (!gst_byte_writer_fill (writer, x, count))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (flxdec, "Failed to decode BRUN packet");
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
flx_decode_delta_fli (GstFlxDec * flxdec, GstByteReader * reader,
|
|
GstByteWriter * writer)
|
|
{
|
|
guint16 start_line, lines;
|
|
guint line_start_i;
|
|
|
|
g_return_val_if_fail (flxdec != NULL, FALSE);
|
|
g_return_val_if_fail (flxdec->delta_data != NULL, FALSE);
|
|
|
|
/* use last frame for delta */
|
|
if (!gst_byte_writer_put_data (writer, flxdec->delta_data, flxdec->size))
|
|
goto error;
|
|
|
|
if (!gst_byte_reader_get_uint16_le (reader, &start_line))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &lines))
|
|
goto error;
|
|
GST_LOG_OBJECT (flxdec, "height %d start line %d line count %d",
|
|
flxdec->hdr.height, start_line, lines);
|
|
|
|
if (start_line + lines > flxdec->hdr.height) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid FLI packet detected. too many lines.");
|
|
return FALSE;
|
|
}
|
|
|
|
line_start_i = flxdec->hdr.width * start_line;
|
|
if (!gst_byte_writer_set_pos (writer, line_start_i))
|
|
goto error;
|
|
|
|
while (lines--) {
|
|
guint8 packets;
|
|
|
|
/* packet count */
|
|
if (!gst_byte_reader_get_uint8 (reader, &packets))
|
|
goto error;
|
|
GST_LOG_OBJECT (flxdec, "have %d packets", packets);
|
|
|
|
while (packets--) {
|
|
/* skip count */
|
|
guint8 skip;
|
|
gint8 count;
|
|
if (!gst_byte_reader_get_uint8 (reader, &skip))
|
|
goto error;
|
|
|
|
/* skip bytes */
|
|
if (!gst_byte_writer_set_pos (writer,
|
|
gst_byte_writer_get_pos (writer) + skip))
|
|
goto error;
|
|
|
|
/* RLE count */
|
|
if (!gst_byte_reader_get_int8 (reader, &count))
|
|
goto error;
|
|
|
|
if (count < 0) {
|
|
guint8 x;
|
|
|
|
/* literal run */
|
|
count = ABS (count);
|
|
GST_LOG_OBJECT (flxdec, "have literal run of size %d at offset %d",
|
|
count, skip);
|
|
|
|
if (skip + count > flxdec->hdr.width) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid FLI packet detected. "
|
|
"line too long.");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gst_byte_reader_get_uint8 (reader, &x))
|
|
goto error;
|
|
if (!gst_byte_writer_fill (writer, x, count))
|
|
goto error;
|
|
} else {
|
|
const guint8 *data;
|
|
|
|
GST_LOG_OBJECT (flxdec, "have replicate run of size %d at offset %d",
|
|
count, skip);
|
|
|
|
if (skip + count > flxdec->hdr.width) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid FLI packet detected. "
|
|
"line too long.");
|
|
return FALSE;
|
|
}
|
|
|
|
/* replicate run */
|
|
if (!gst_byte_reader_get_data (reader, count, &data))
|
|
goto error;
|
|
if (!gst_byte_writer_put_data (writer, data, count))
|
|
goto error;
|
|
}
|
|
}
|
|
line_start_i += flxdec->hdr.width;
|
|
if (!gst_byte_writer_set_pos (writer, line_start_i))
|
|
goto error;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (flxdec, "Failed to decode FLI packet");
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
flx_decode_delta_flc (GstFlxDec * flxdec, GstByteReader * reader,
|
|
GstByteWriter * writer)
|
|
{
|
|
guint16 lines, start_l;
|
|
|
|
g_return_val_if_fail (flxdec != NULL, FALSE);
|
|
g_return_val_if_fail (flxdec->delta_data != NULL, FALSE);
|
|
|
|
/* use last frame for delta */
|
|
if (!gst_byte_writer_put_data (writer, flxdec->delta_data, flxdec->size))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &lines))
|
|
goto error;
|
|
|
|
if (lines > flxdec->hdr.height) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid FLC packet detected. too many lines.");
|
|
return FALSE;
|
|
}
|
|
|
|
start_l = lines;
|
|
|
|
while (lines) {
|
|
guint16 opcode;
|
|
|
|
if (!gst_byte_writer_set_pos (writer,
|
|
flxdec->hdr.width * (start_l - lines)))
|
|
goto error;
|
|
|
|
/* process opcode(s) */
|
|
while (TRUE) {
|
|
if (!gst_byte_reader_get_uint16_le (reader, &opcode))
|
|
goto error;
|
|
if ((opcode & 0xc000) == 0)
|
|
break;
|
|
|
|
if ((opcode & 0xc000) == 0xc000) {
|
|
/* line skip count */
|
|
gulong skip = (0x10000 - opcode);
|
|
if (skip > flxdec->hdr.height) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid FLC packet detected. "
|
|
"skip line count too big.");
|
|
return FALSE;
|
|
}
|
|
start_l += skip;
|
|
if (!gst_byte_writer_set_pos (writer,
|
|
gst_byte_writer_get_pos (writer) + flxdec->hdr.width * skip))
|
|
goto error;
|
|
} else {
|
|
/* last pixel */
|
|
if (!gst_byte_writer_set_pos (writer,
|
|
gst_byte_writer_get_pos (writer) + flxdec->hdr.width))
|
|
goto error;
|
|
if (!gst_byte_writer_put_uint8 (writer, opcode & 0xff))
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* last opcode is the packet count */
|
|
GST_LOG_OBJECT (flxdec, "have %d packets", opcode);
|
|
while (opcode--) {
|
|
/* skip count */
|
|
guint8 skip;
|
|
gint8 count;
|
|
|
|
if (!gst_byte_reader_get_uint8 (reader, &skip))
|
|
goto error;
|
|
if (!gst_byte_writer_set_pos (writer,
|
|
gst_byte_writer_get_pos (writer) + skip))
|
|
goto error;
|
|
|
|
/* RLE count */
|
|
if (!gst_byte_reader_get_int8 (reader, &count))
|
|
goto error;
|
|
|
|
if (count < 0) {
|
|
guint16 x;
|
|
|
|
/* replicate word run */
|
|
count = ABS (count);
|
|
|
|
GST_LOG_OBJECT (flxdec, "have replicate run of size %d at offset %d",
|
|
count, skip);
|
|
|
|
if (skip + count > flxdec->hdr.width) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid FLC packet detected. "
|
|
"line too long.");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gst_byte_reader_get_uint16_le (reader, &x))
|
|
goto error;
|
|
|
|
while (count--) {
|
|
if (!gst_byte_writer_put_uint16_le (writer, x)) {
|
|
goto error;
|
|
}
|
|
}
|
|
} else {
|
|
GST_LOG_OBJECT (flxdec, "have literal run of size %d at offset %d",
|
|
count, skip);
|
|
|
|
if (skip + count > flxdec->hdr.width) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid FLC packet detected. "
|
|
"line too long.");
|
|
return FALSE;
|
|
}
|
|
|
|
while (count--) {
|
|
guint16 x;
|
|
|
|
if (!gst_byte_reader_get_uint16_le (reader, &x))
|
|
goto error;
|
|
if (!gst_byte_writer_put_uint16_le (writer, x))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
lines--;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (flxdec, "Failed to decode FLI packet");
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
_read_flx_header (GstFlxDec * flxdec, GstByteReader * reader, FlxHeader * flxh)
|
|
{
|
|
memset (flxh, 0, sizeof (*flxh));
|
|
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->size))
|
|
goto error;
|
|
if (flxh->size < FlxHeaderSize) {
|
|
GST_ERROR_OBJECT (flxdec, "Invalid file size in the header");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->type))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->frames))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->width))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->height))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->depth))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->flags))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->speed))
|
|
goto error;
|
|
if (!gst_byte_reader_skip (reader, 2)) /* reserved */
|
|
goto error;
|
|
/* FLC */
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->created))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->creator))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->updated))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->updater))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->aspect_dx))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->aspect_dy))
|
|
goto error;
|
|
/* EGI */
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->ext_flags))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->keyframes))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->totalframes))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->req_memory))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->max_regions))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint16_le (reader, &flxh->transp_num))
|
|
goto error;
|
|
if (!gst_byte_reader_skip (reader, 24)) /* reserved */
|
|
goto error;
|
|
/* FLC */
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->oframe1))
|
|
goto error;
|
|
if (!gst_byte_reader_get_uint32_le (reader, &flxh->oframe2))
|
|
goto error;
|
|
if (!gst_byte_reader_skip (reader, 40)) /* reserved */
|
|
goto error;
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
GST_ERROR_OBJECT (flxdec, "Error reading file header");
|
|
return FALSE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_flxdec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
|
|
{
|
|
GstByteReader reader;
|
|
GstBuffer *input;
|
|
GstMapInfo map_info;
|
|
GstCaps *caps;
|
|
guint available;
|
|
GstFlowReturn res = GST_FLOW_OK;
|
|
|
|
GstFlxDec *flxdec;
|
|
FlxHeader *flxh;
|
|
|
|
g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
|
|
flxdec = (GstFlxDec *) parent;
|
|
g_return_val_if_fail (flxdec != NULL, GST_FLOW_ERROR);
|
|
|
|
gst_adapter_push (flxdec->adapter, buf);
|
|
available = gst_adapter_available (flxdec->adapter);
|
|
input = gst_adapter_get_buffer (flxdec->adapter, available);
|
|
if (!gst_buffer_map (input, &map_info, GST_MAP_READ)) {
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, DECODE,
|
|
("%s", "Failed to map buffer"), (NULL));
|
|
goto error;
|
|
}
|
|
gst_byte_reader_init (&reader, map_info.data, map_info.size);
|
|
|
|
if (flxdec->state == GST_FLXDEC_READ_HEADER) {
|
|
if (available >= FlxHeaderSize) {
|
|
GstByteReader header;
|
|
GstCaps *templ;
|
|
|
|
if (!gst_byte_reader_get_sub_reader (&reader, &header, FlxHeaderSize)) {
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, DECODE,
|
|
("%s", "Could not read header"), (NULL));
|
|
goto unmap_input_error;
|
|
}
|
|
gst_adapter_flush (flxdec->adapter, FlxHeaderSize);
|
|
available -= FlxHeaderSize;
|
|
|
|
if (!_read_flx_header (flxdec, &header, &flxdec->hdr)) {
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, DECODE,
|
|
("%s", "Failed to parse header"), (NULL));
|
|
goto unmap_input_error;
|
|
}
|
|
|
|
flxh = &flxdec->hdr;
|
|
|
|
/* check header */
|
|
if (flxh->type != FLX_MAGICHDR_FLI &&
|
|
flxh->type != FLX_MAGICHDR_FLC && flxh->type != FLX_MAGICHDR_FLX) {
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, WRONG_TYPE, (NULL),
|
|
("not a flx file (type %x)", flxh->type));
|
|
goto unmap_input_error;
|
|
}
|
|
|
|
GST_INFO_OBJECT (flxdec, "size : %d", flxh->size);
|
|
GST_INFO_OBJECT (flxdec, "frames : %d", flxh->frames);
|
|
GST_INFO_OBJECT (flxdec, "width : %d", flxh->width);
|
|
GST_INFO_OBJECT (flxdec, "height : %d", flxh->height);
|
|
GST_INFO_OBJECT (flxdec, "depth : %d", flxh->depth);
|
|
GST_INFO_OBJECT (flxdec, "speed : %d", flxh->speed);
|
|
|
|
flxdec->next_time = 0;
|
|
|
|
if (flxh->type == FLX_MAGICHDR_FLI) {
|
|
flxdec->frame_time = JIFFIE * flxh->speed;
|
|
} else if (flxh->speed == 0) {
|
|
flxdec->frame_time = GST_SECOND / 70;
|
|
} else {
|
|
flxdec->frame_time = flxh->speed * GST_MSECOND;
|
|
}
|
|
|
|
flxdec->duration = flxh->frames * flxdec->frame_time;
|
|
GST_LOG ("duration : %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (flxdec->duration));
|
|
|
|
templ = gst_pad_get_pad_template_caps (flxdec->srcpad);
|
|
caps = gst_caps_copy (templ);
|
|
gst_caps_unref (templ);
|
|
gst_caps_set_simple (caps,
|
|
"width", G_TYPE_INT, flxh->width,
|
|
"height", G_TYPE_INT, flxh->height,
|
|
"framerate", GST_TYPE_FRACTION, (gint) GST_MSECOND,
|
|
(gint) flxdec->frame_time / 1000, NULL);
|
|
|
|
gst_pad_set_caps (flxdec->srcpad, caps);
|
|
gst_caps_unref (caps);
|
|
|
|
/* zero means 8 */
|
|
if (flxh->depth == 0)
|
|
flxh->depth = 8;
|
|
|
|
if (flxh->depth != 8) {
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, WRONG_TYPE,
|
|
("%s", "Don't know how to decode non 8 bit depth streams"), (NULL));
|
|
goto unmap_input_error;
|
|
}
|
|
|
|
flxdec->converter =
|
|
flx_colorspace_converter_new (flxh->width, flxh->height);
|
|
|
|
if (flxh->type == FLX_MAGICHDR_FLC || flxh->type == FLX_MAGICHDR_FLX) {
|
|
GST_INFO_OBJECT (flxdec, "(FLC) aspect_dx : %d", flxh->aspect_dx);
|
|
GST_INFO_OBJECT (flxdec, "(FLC) aspect_dy : %d", flxh->aspect_dy);
|
|
GST_INFO_OBJECT (flxdec, "(FLC) oframe1 : 0x%08x", flxh->oframe1);
|
|
GST_INFO_OBJECT (flxdec, "(FLC) oframe2 : 0x%08x", flxh->oframe2);
|
|
}
|
|
|
|
flxdec->size = ((guint) flxh->width * (guint) flxh->height);
|
|
if (flxdec->size >= G_MAXSIZE / 4) {
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, DECODE,
|
|
("%s", "Cannot allocate required memory"), (NULL));
|
|
goto unmap_input_error;
|
|
}
|
|
|
|
/* create delta and output frame */
|
|
flxdec->frame_data = g_malloc (flxdec->size);
|
|
flxdec->delta_data = g_malloc (flxdec->size);
|
|
|
|
flxdec->state = GST_FLXDEC_PLAYING;
|
|
}
|
|
} else if (flxdec->state == GST_FLXDEC_PLAYING) {
|
|
GstBuffer *out;
|
|
|
|
/* while we have enough data in the adapter */
|
|
while (available >= FlxFrameChunkSize && res == GST_FLOW_OK) {
|
|
guint32 size;
|
|
guint16 type;
|
|
|
|
if (!gst_byte_reader_get_uint32_le (&reader, &size))
|
|
goto parse_error;
|
|
if (available < size)
|
|
goto need_more_data;
|
|
|
|
available -= size;
|
|
gst_adapter_flush (flxdec->adapter, size);
|
|
|
|
if (!gst_byte_reader_get_uint16_le (&reader, &type))
|
|
goto parse_error;
|
|
|
|
switch (type) {
|
|
case FLX_FRAME_TYPE:{
|
|
GstByteReader chunks;
|
|
GstByteWriter writer;
|
|
guint16 n_chunks;
|
|
GstMapInfo map;
|
|
|
|
GST_LOG_OBJECT (flxdec, "Have frame type 0x%02x of size %d", type,
|
|
size);
|
|
|
|
if (!gst_byte_reader_get_sub_reader (&reader, &chunks,
|
|
size - FlxFrameChunkSize))
|
|
goto parse_error;
|
|
|
|
if (!gst_byte_reader_get_uint16_le (&chunks, &n_chunks))
|
|
goto parse_error;
|
|
GST_LOG_OBJECT (flxdec, "Have %d chunks", n_chunks);
|
|
|
|
if (n_chunks == 0)
|
|
break;
|
|
if (!gst_byte_reader_skip (&chunks, 8)) /* reserved */
|
|
goto parse_error;
|
|
|
|
gst_byte_writer_init_with_data (&writer, flxdec->frame_data,
|
|
flxdec->size, TRUE);
|
|
|
|
/* decode chunks */
|
|
if (!flx_decode_chunks (flxdec, n_chunks, &chunks, &writer)) {
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, DECODE,
|
|
("%s", "Could not decode chunk"), NULL);
|
|
goto unmap_input_error;
|
|
}
|
|
gst_byte_writer_reset (&writer);
|
|
|
|
/* save copy of the current frame for possible delta. */
|
|
memcpy (flxdec->delta_data, flxdec->frame_data, flxdec->size);
|
|
|
|
out = gst_buffer_new_and_alloc (flxdec->size * 4);
|
|
if (!gst_buffer_map (out, &map, GST_MAP_WRITE)) {
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, DECODE,
|
|
("%s", "Could not map output buffer"), NULL);
|
|
gst_buffer_unref (out);
|
|
goto unmap_input_error;
|
|
}
|
|
|
|
/* convert current frame. */
|
|
flx_colorspace_convert (flxdec->converter, flxdec->frame_data,
|
|
map.data);
|
|
gst_buffer_unmap (out, &map);
|
|
|
|
GST_BUFFER_TIMESTAMP (out) = flxdec->next_time;
|
|
flxdec->next_time += flxdec->frame_time;
|
|
|
|
res = gst_pad_push (flxdec->srcpad, out);
|
|
break;
|
|
}
|
|
default:
|
|
GST_DEBUG_OBJECT (flxdec, "Unknown frame type 0x%02x, skipping %d",
|
|
type, size);
|
|
if (!gst_byte_reader_skip (&reader, size - FlxFrameChunkSize))
|
|
goto parse_error;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gst_buffer_unmap (input, &map_info);
|
|
gst_buffer_unref (input);
|
|
|
|
need_more_data:
|
|
return res;
|
|
|
|
/* ERRORS */
|
|
parse_error:
|
|
GST_ELEMENT_ERROR (flxdec, STREAM, DECODE,
|
|
("%s", "Failed to parse stream"), (NULL));
|
|
unmap_input_error:
|
|
gst_buffer_unmap (input, &map_info);
|
|
gst_buffer_unref (input);
|
|
error:
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_flxdec_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstFlxDec *flxdec;
|
|
GstStateChangeReturn ret;
|
|
|
|
flxdec = GST_FLXDEC (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
gst_adapter_clear (flxdec->adapter);
|
|
flxdec->state = GST_FLXDEC_READ_HEADER;
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
if (flxdec->frame_data) {
|
|
g_free (flxdec->frame_data);
|
|
flxdec->frame_data = NULL;
|
|
}
|
|
if (flxdec->delta_data) {
|
|
g_free (flxdec->delta_data);
|
|
flxdec->delta_data = NULL;
|
|
}
|
|
if (flxdec->converter) {
|
|
flx_colorspace_converter_destroy (flxdec->converter);
|
|
flxdec->converter = NULL;
|
|
}
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
return gst_element_register (plugin, "flxdec",
|
|
GST_RANK_PRIMARY, GST_TYPE_FLXDEC);
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
flxdec,
|
|
"FLC/FLI/FLX video decoder",
|
|
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|