mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-02 04:22:27 +00:00
Add VMnc decoder.
Original commit message from CVS: * configure.ac: * gst/vmnc/Makefile.am: * gst/vmnc/vmncdec.c: (gst_vmnc_dec_base_init), (gst_vmnc_dec_class_init), (gst_vmnc_dec_init), (gst_vmnc_dec_reset), (vmnc_handle_wmvi_rectangle), (render_colour_cursor), (render_cursor), (vmnc_make_buffer), (vmnc_handle_wmvd_rectangle), (vmnc_handle_wmve_rectangle), (vmnc_handle_wmvf_rectangle), (vmnc_handle_wmvg_rectangle), (vmnc_handle_wmvh_rectangle), (vmnc_handle_wmvj_rectangle), (render_raw_tile), (render_subrect), (vmnc_handle_raw_rectangle), (vmnc_handle_hextile_rectangle), (vmnc_handle_packet), (vmnc_dec_setcaps), (vmnc_dec_chain), (vmnc_dec_change_state), (vmnc_dec_set_property), (vmnc_dec_get_property), (plugin_init): Add VMnc decoder. Still missing support for: - rectangle types I didn't find in my samples (e.g. copy, RRE, ZRLE) - alpha-composited cursors
This commit is contained in:
parent
d84dc3a4af
commit
1a428b03ed
4 changed files with 986 additions and 0 deletions
21
ChangeLog
21
ChangeLog
|
@ -1,3 +1,24 @@
|
|||
2007-03-03 Michael Smith <msmith@fluendo.com>
|
||||
|
||||
* configure.ac:
|
||||
* gst/vmnc/Makefile.am:
|
||||
* gst/vmnc/vmncdec.c: (gst_vmnc_dec_base_init),
|
||||
(gst_vmnc_dec_class_init), (gst_vmnc_dec_init),
|
||||
(gst_vmnc_dec_reset), (vmnc_handle_wmvi_rectangle),
|
||||
(render_colour_cursor), (render_cursor), (vmnc_make_buffer),
|
||||
(vmnc_handle_wmvd_rectangle), (vmnc_handle_wmve_rectangle),
|
||||
(vmnc_handle_wmvf_rectangle), (vmnc_handle_wmvg_rectangle),
|
||||
(vmnc_handle_wmvh_rectangle), (vmnc_handle_wmvj_rectangle),
|
||||
(render_raw_tile), (render_subrect), (vmnc_handle_raw_rectangle),
|
||||
(vmnc_handle_hextile_rectangle), (vmnc_handle_packet),
|
||||
(vmnc_dec_setcaps), (vmnc_dec_chain), (vmnc_dec_change_state),
|
||||
(vmnc_dec_set_property), (vmnc_dec_get_property), (plugin_init):
|
||||
Add VMnc decoder.
|
||||
Still missing support for:
|
||||
- rectangle types I didn't find in my samples (e.g. copy, RRE,
|
||||
ZRLE)
|
||||
- alpha-composited cursors
|
||||
|
||||
2007-03-03 David Schleef <ds@schleef.org>
|
||||
|
||||
* gst-libs/gst/app/Makefile.am:
|
||||
|
|
|
@ -1071,6 +1071,7 @@ gst/qtdemux/Makefile
|
|||
gst/tta/Makefile
|
||||
gst/videocrop/Makefile
|
||||
gst/videoparse/Makefile
|
||||
gst/vmnc/Makefile
|
||||
gst/xingheader/Makefile
|
||||
gst/real/Makefile
|
||||
gst/y4m/Makefile
|
||||
|
|
6
gst/vmnc/Makefile.am
Normal file
6
gst/vmnc/Makefile.am
Normal file
|
@ -0,0 +1,6 @@
|
|||
plugin_LTLIBRARIES = libgstvmware.la
|
||||
|
||||
libgstvmware_la_SOURCES = vmncdec.c
|
||||
libgstvmware_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(THEORA_CFLAGS)
|
||||
libgstvmware_la_LIBADD = $(GST_LIBS)
|
||||
libgstvmware_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
958
gst/vmnc/vmncdec.c
Normal file
958
gst/vmnc/vmncdec.c
Normal file
|
@ -0,0 +1,958 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2007 Michael Smith <msmith@xiph.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a decoder for the VMWare VMnc video codec, which VMWare uses for
|
||||
* recording * of virtual machine instances.
|
||||
* It's essentially a serialisation of RFB (the VNC protocol)
|
||||
* 'FramebufferUpdate' messages, with some special encoding-types for VMnc
|
||||
* extensions. There's some documentation (with fixes from VMWare employees) at:
|
||||
* http://wiki.multimedia.cx/index.php?title=VMware_Video
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <string.h>
|
||||
|
||||
#define GST_CAT_DEFAULT vmnc_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
#define GST_TYPE_VMNC_DEC \
|
||||
(gst_vmnc_dec_get_type())
|
||||
#define GST_VMNC_DEC(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VMNC_DEC,GstVMncDec))
|
||||
|
||||
#define RFB_GET_UINT32(ptr) GUINT32_FROM_BE (*(guint32 *)(ptr))
|
||||
#define RFB_GET_UINT16(ptr) GUINT16_FROM_BE (*(guint16 *)(ptr))
|
||||
#define RFB_GET_UINT8(ptr) (*(guint8 *)(ptr))
|
||||
|
||||
enum
|
||||
{
|
||||
ARG_0,
|
||||
};
|
||||
|
||||
#define MAKE_TYPE(a,b,c,d) ((a<<24)|(b<<16)|(c<<8)|d)
|
||||
enum
|
||||
{
|
||||
TYPE_RAW = 0,
|
||||
TYPE_COPY = 1,
|
||||
TYPE_RRE = 2,
|
||||
TYPE_CoRRE = 4,
|
||||
TYPE_HEXTILE = 5,
|
||||
|
||||
TYPE_WMVd = MAKE_TYPE ('W', 'M', 'V', 'd'),
|
||||
TYPE_WMVe = MAKE_TYPE ('W', 'M', 'V', 'e'),
|
||||
TYPE_WMVf = MAKE_TYPE ('W', 'M', 'V', 'f'),
|
||||
TYPE_WMVg = MAKE_TYPE ('W', 'M', 'V', 'g'),
|
||||
TYPE_WMVh = MAKE_TYPE ('W', 'M', 'V', 'h'),
|
||||
TYPE_WMVi = MAKE_TYPE ('W', 'M', 'V', 'i'),
|
||||
TYPE_WMVj = MAKE_TYPE ('W', 'M', 'V', 'j')
|
||||
};
|
||||
|
||||
struct RFBFormat
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int stride;
|
||||
int bytes_per_pixel;
|
||||
int depth;
|
||||
int big_endian;
|
||||
|
||||
guint8 descriptor[16]; /* The raw format descriptor block */
|
||||
};
|
||||
|
||||
enum CursorType
|
||||
{
|
||||
CURSOR_COLOUR = 0,
|
||||
CURSOR_ALPHA = 1
|
||||
};
|
||||
|
||||
struct Cursor
|
||||
{
|
||||
enum CursorType type;
|
||||
int visible;
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
int hot_x;
|
||||
int hot_y;
|
||||
guint8 *cursordata;
|
||||
guint8 *cursormask;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstElement element;
|
||||
|
||||
GstPad *sinkpad;
|
||||
GstPad *srcpad;
|
||||
|
||||
GstCaps *caps;
|
||||
|
||||
int framerate_num;
|
||||
int framerate_denom;
|
||||
|
||||
struct Cursor cursor;
|
||||
struct RFBFormat format;
|
||||
guint8 *imagedata;
|
||||
} GstVMncDec;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstElementClass parent_class;
|
||||
} GstVMncDecClass;
|
||||
|
||||
static const GstElementDetails vmnc_dec_details =
|
||||
GST_ELEMENT_DETAILS ("VMnc video decoder",
|
||||
"Codec/Decoder/Video",
|
||||
"Decode VMnc to raw (RGB) video",
|
||||
"Michael Smith <msmith@xiph.org>");
|
||||
|
||||
static GstStaticPadTemplate vmnc_dec_src_factory =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("video/x-raw-rgb"));
|
||||
|
||||
static GstStaticPadTemplate vmnc_dec_sink_factory =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("video/x-vmnc, version=(int)1, "
|
||||
"framerate=(fraction)[0, max], "
|
||||
"width=(int)[0, max], " "height=(int)[0, max]")
|
||||
);
|
||||
|
||||
GST_BOILERPLATE (GstVMncDec, gst_vmnc_dec, GstElement, GST_TYPE_ELEMENT);
|
||||
|
||||
static void vmnc_dec_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
static void vmnc_dec_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
|
||||
static GstFlowReturn vmnc_dec_chain (GstPad * pad, GstBuffer * buffer);
|
||||
static gboolean vmnc_dec_setcaps (GstPad * pad, GstCaps * caps);
|
||||
static GstStateChangeReturn vmnc_dec_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
static void
|
||||
gst_vmnc_dec_base_init (gpointer g_class)
|
||||
{
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
||||
|
||||
gst_element_class_add_pad_template (element_class,
|
||||
gst_static_pad_template_get (&vmnc_dec_src_factory));
|
||||
gst_element_class_add_pad_template (element_class,
|
||||
gst_static_pad_template_get (&vmnc_dec_sink_factory));
|
||||
gst_element_class_set_details (element_class, &vmnc_dec_details);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_vmnc_dec_class_init (GstVMncDecClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
|
||||
|
||||
gobject_class->set_property = vmnc_dec_set_property;
|
||||
gobject_class->get_property = vmnc_dec_get_property;
|
||||
|
||||
gstelement_class->change_state = vmnc_dec_change_state;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (vmnc_debug, "vmncdec", 0, "VMnc decoder");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_vmnc_dec_init (GstVMncDec * dec, GstVMncDecClass * g_class)
|
||||
{
|
||||
dec->sinkpad =
|
||||
gst_pad_new_from_static_template (&vmnc_dec_sink_factory, "sink");
|
||||
gst_pad_set_chain_function (dec->sinkpad, vmnc_dec_chain);
|
||||
gst_pad_set_setcaps_function (dec->sinkpad, vmnc_dec_setcaps);
|
||||
gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
|
||||
|
||||
dec->srcpad = gst_pad_new_from_static_template (&vmnc_dec_src_factory, "src");
|
||||
gst_pad_use_fixed_caps (dec->srcpad);
|
||||
|
||||
gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_vmnc_dec_reset (GstVMncDec * dec)
|
||||
{
|
||||
if (dec->caps) {
|
||||
gst_caps_unref (dec->caps);
|
||||
dec->caps = NULL;
|
||||
}
|
||||
if (dec->imagedata) {
|
||||
g_free (dec->imagedata);
|
||||
dec->imagedata = NULL;
|
||||
}
|
||||
|
||||
if (dec->cursor.cursordata) {
|
||||
g_free (dec->cursor.cursordata);
|
||||
dec->cursor.cursordata = NULL;
|
||||
}
|
||||
if (dec->cursor.cursormask) {
|
||||
g_free (dec->cursor.cursormask);
|
||||
dec->cursor.cursormask = NULL;
|
||||
}
|
||||
dec->cursor.visible = 0;
|
||||
|
||||
/* Use these as defaults if the container doesn't provide anything */
|
||||
dec->framerate_num = 5;
|
||||
dec->framerate_denom = 1;
|
||||
}
|
||||
|
||||
struct RfbRectangle
|
||||
{
|
||||
guint16 x;
|
||||
guint16 y;
|
||||
guint16 width;
|
||||
guint16 height;
|
||||
|
||||
gint32 type;
|
||||
};
|
||||
|
||||
/* Rectangle handling functions.
|
||||
* Return number of bytes consumed, or < 0 on error
|
||||
*/
|
||||
typedef int (*rectangle_handler) (GstVMncDec * dec, struct RfbRectangle * rect,
|
||||
guint8 * data, int len);
|
||||
|
||||
static int
|
||||
vmnc_handle_wmvi_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
GstCaps *caps;
|
||||
gint bpp, tc;
|
||||
guint32 redmask, greenmask, bluemask;
|
||||
guint32 endianness, dataendianness;
|
||||
|
||||
/* A WMVi rectangle has a 16byte payload */
|
||||
if (len < 16) {
|
||||
GST_WARNING_OBJECT (dec, "Bad WMVi rect: too short");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* We only compare 13 bytes; ignoring the 3 padding bytes at the end */
|
||||
if (memcmp (data, dec->format.descriptor, 13) == 0) {
|
||||
/* Nothing changed, so just exit */
|
||||
return 16;
|
||||
}
|
||||
|
||||
/* Store the whole block for simple comparison later */
|
||||
memcpy (dec->format.descriptor, data, 16);
|
||||
|
||||
if (rect->x != 0 || rect->y != 0) {
|
||||
GST_WARNING_OBJECT (dec, "Bad WMVi rect: wrong coordinates");
|
||||
return -1;
|
||||
}
|
||||
|
||||
bpp = data[0];
|
||||
dec->format.depth = data[1];
|
||||
dec->format.big_endian = data[2];
|
||||
dataendianness = data[2] ? G_BIG_ENDIAN : G_LITTLE_ENDIAN;
|
||||
tc = data[3];
|
||||
|
||||
if (bpp != 8 && bpp != 16 && bpp != 32) {
|
||||
GST_WARNING_OBJECT (dec, "Bad bpp value: %d", bpp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!tc) {
|
||||
GST_WARNING_OBJECT (dec, "Paletted video not supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dec->format.bytes_per_pixel = bpp / 8;
|
||||
dec->format.width = rect->width;
|
||||
dec->format.height = rect->height;
|
||||
|
||||
redmask = (guint32) (RFB_GET_UINT16 (data + 4)) << data[10];
|
||||
greenmask = (guint32) (RFB_GET_UINT16 (data + 6)) << data[11];
|
||||
bluemask = (guint32) (RFB_GET_UINT16 (data + 8)) << data[12];
|
||||
|
||||
GST_DEBUG_OBJECT (dec, "Red: mask %d, shift %d",
|
||||
RFB_GET_UINT16 (data + 4), data[10]);
|
||||
GST_DEBUG_OBJECT (dec, "Green: mask %d, shift %d",
|
||||
RFB_GET_UINT16 (data + 6), data[11]);
|
||||
GST_DEBUG_OBJECT (dec, "Blue: mask %d, shift %d",
|
||||
RFB_GET_UINT16 (data + 8), data[12]);
|
||||
GST_DEBUG_OBJECT (dec, "BPP: %d. endianness: %s", bpp,
|
||||
data[2] ? "big" : "little");
|
||||
|
||||
/* GStreamer's RGB caps are a bit weird. */
|
||||
if (bpp == 8) {
|
||||
endianness = G_BYTE_ORDER; /* Doesn't matter */
|
||||
} else if (bpp == 16) {
|
||||
/* We require host-endian. */
|
||||
endianness = G_BYTE_ORDER;
|
||||
} else { /* bpp == 32 */
|
||||
/* We require big endian */
|
||||
endianness = G_BIG_ENDIAN;
|
||||
if (endianness != dataendianness) {
|
||||
redmask = GUINT32_SWAP_LE_BE (redmask);
|
||||
greenmask = GUINT32_SWAP_LE_BE (greenmask);
|
||||
bluemask = GUINT32_SWAP_LE_BE (bluemask);
|
||||
}
|
||||
}
|
||||
|
||||
caps = gst_caps_new_simple ("video/x-raw-rgb",
|
||||
"framerate", GST_TYPE_FRACTION, dec->framerate_num, dec->framerate_denom,
|
||||
"pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
|
||||
"width", G_TYPE_INT, rect->width, "height", G_TYPE_INT, rect->height,
|
||||
"bpp", G_TYPE_INT, bpp,
|
||||
"depth", G_TYPE_INT, dec->format.depth,
|
||||
"endianness", G_TYPE_INT, endianness,
|
||||
"red_mask", G_TYPE_INT, redmask,
|
||||
"green_mask", G_TYPE_INT, greenmask,
|
||||
"blue_mask", G_TYPE_INT, bluemask, NULL);
|
||||
gst_pad_set_caps (dec->srcpad, caps);
|
||||
|
||||
if (dec->caps)
|
||||
gst_caps_unref (dec->caps);
|
||||
dec->caps = caps;
|
||||
|
||||
if (dec->imagedata)
|
||||
g_free (dec->imagedata);
|
||||
dec->imagedata = g_malloc (dec->format.width * dec->format.height *
|
||||
dec->format.bytes_per_pixel);
|
||||
|
||||
dec->format.stride = dec->format.width * dec->format.bytes_per_pixel;
|
||||
|
||||
return 16;
|
||||
}
|
||||
|
||||
static void
|
||||
render_colour_cursor (GstVMncDec * dec, guint8 * data, int x, int y,
|
||||
int off_x, int off_y, int width, int height)
|
||||
{
|
||||
int i, j;
|
||||
guint8 *dstraw = data + dec->format.stride * y +
|
||||
dec->format.bytes_per_pixel * x;
|
||||
guint8 *srcraw = dec->cursor.cursordata +
|
||||
dec->cursor.width * dec->format.bytes_per_pixel * off_y;
|
||||
guint8 *maskraw = dec->cursor.cursormask +
|
||||
dec->cursor.width * dec->format.bytes_per_pixel * off_y;
|
||||
|
||||
/* Boundchecking done by caller; this is just the renderer inner loop */
|
||||
if (dec->format.bytes_per_pixel == 1) {
|
||||
guint8 *dst = dstraw;
|
||||
guint8 *src = srcraw;
|
||||
guint8 *mask = maskraw;
|
||||
|
||||
for (i = 0; i < height; i++) {
|
||||
for (j = 0; j < width; j++) {
|
||||
dst[j] = (dst[j] & src[j]) ^ mask[j];
|
||||
}
|
||||
dst += dec->format.width;
|
||||
src += dec->cursor.width;
|
||||
mask += dec->cursor.width;
|
||||
}
|
||||
} else if (dec->format.bytes_per_pixel == 2) {
|
||||
guint16 *dst = (guint16 *) dstraw;
|
||||
guint16 *src = (guint16 *) srcraw;
|
||||
guint16 *mask = (guint16 *) maskraw;
|
||||
|
||||
for (i = 0; i < height; i++) {
|
||||
for (j = 0; j < width; j++) {
|
||||
dst[j] = (dst[j] & src[j]) ^ mask[j];
|
||||
}
|
||||
dst += dec->format.width;
|
||||
src += dec->cursor.width;
|
||||
mask += dec->cursor.width;
|
||||
}
|
||||
} else {
|
||||
guint32 *dst = (guint32 *) dstraw;
|
||||
guint32 *src = (guint32 *) srcraw;
|
||||
guint32 *mask = (guint32 *) maskraw;
|
||||
|
||||
for (i = 0; i < height; i++) {
|
||||
for (j = 0; j < width; j++) {
|
||||
dst[j] = (dst[j] & src[j]) ^ mask[j];
|
||||
}
|
||||
dst += dec->format.width;
|
||||
src += dec->cursor.width;
|
||||
mask += dec->cursor.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
render_cursor (GstVMncDec * dec, guint8 * data)
|
||||
{
|
||||
/* First, figure out the portion of the cursor that's on-screen */
|
||||
/* X,Y of top-left of cursor */
|
||||
int x = dec->cursor.x - dec->cursor.hot_x;
|
||||
int y = dec->cursor.y - dec->cursor.hot_y;
|
||||
|
||||
/* Width, height of rendered portion of cursor */
|
||||
int width = dec->cursor.width;
|
||||
int height = dec->cursor.height;
|
||||
|
||||
/* X,Y offset of rendered portion of cursor */
|
||||
int off_x = 0;
|
||||
int off_y = 0;
|
||||
|
||||
if (x < 0) {
|
||||
off_x = -x;
|
||||
width += x;
|
||||
x = 0;
|
||||
}
|
||||
if (x + width > dec->format.width)
|
||||
width = dec->format.width - x;
|
||||
if (y < 0) {
|
||||
off_y = -y;
|
||||
height += y;
|
||||
y = 0;
|
||||
}
|
||||
if (y + height > dec->format.height)
|
||||
height = dec->format.height - y;
|
||||
|
||||
if (dec->cursor.type == CURSOR_COLOUR) {
|
||||
render_colour_cursor (dec, data, x, y, off_x, off_y, width, height);
|
||||
} else {
|
||||
/* Alpha cursor. */
|
||||
/* TODO: Implement me! */
|
||||
GST_WARNING_OBJECT (dec, "Alpha composited cursors not yet implemented");
|
||||
}
|
||||
}
|
||||
|
||||
static GstBuffer *
|
||||
vmnc_make_buffer (GstVMncDec * dec, GstBuffer * inbuf)
|
||||
{
|
||||
int size = dec->format.stride * dec->format.height;
|
||||
GstBuffer *buf = gst_buffer_new_and_alloc (size);
|
||||
guint8 *data = GST_BUFFER_DATA (buf);
|
||||
|
||||
memcpy (data, dec->imagedata, size);
|
||||
|
||||
if (dec->cursor.visible) {
|
||||
render_cursor (dec, data);
|
||||
}
|
||||
|
||||
gst_buffer_stamp (buf, inbuf);
|
||||
|
||||
gst_buffer_set_caps (buf, dec->caps);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
vmnc_handle_wmvd_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
/* Cursor data. */
|
||||
int datalen = 2;
|
||||
int type, size;
|
||||
|
||||
if (len < datalen) {
|
||||
GST_WARNING_OBJECT (dec, "Cursor data too short");
|
||||
return -1;
|
||||
}
|
||||
|
||||
type = RFB_GET_UINT8 (data);
|
||||
|
||||
if (type == CURSOR_COLOUR) {
|
||||
datalen += rect->width * rect->height * dec->format.bytes_per_pixel * 2;
|
||||
} else if (type == CURSOR_ALPHA) {
|
||||
datalen += rect->width * rect->height * 4;
|
||||
} else {
|
||||
GST_WARNING_OBJECT (dec, "Unknown cursor type: %d", type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (len < datalen) {
|
||||
GST_WARNING_OBJECT (dec, "Cursor data too short");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dec->cursor.type = type;
|
||||
dec->cursor.width = rect->width;
|
||||
dec->cursor.height = rect->height;
|
||||
dec->cursor.type = type;
|
||||
dec->cursor.hot_x = rect->x;
|
||||
dec->cursor.hot_y = rect->y;
|
||||
|
||||
if (dec->cursor.cursordata)
|
||||
g_free (dec->cursor.cursordata);
|
||||
if (dec->cursor.cursormask)
|
||||
g_free (dec->cursor.cursormask);
|
||||
|
||||
if (type == 0) {
|
||||
size = rect->width * rect->height * dec->format.bytes_per_pixel;
|
||||
dec->cursor.cursordata = g_malloc (size);
|
||||
dec->cursor.cursormask = g_malloc (size);
|
||||
memcpy (dec->cursor.cursordata, data + 2, size);
|
||||
memcpy (dec->cursor.cursormask, data + 2 + size, size);
|
||||
} else {
|
||||
dec->cursor.cursordata = g_malloc (rect->width * rect->height * 4);
|
||||
memcpy (dec->cursor.cursordata, data + 2, rect->width * rect->height * 4);
|
||||
}
|
||||
|
||||
return datalen;
|
||||
}
|
||||
|
||||
static int
|
||||
vmnc_handle_wmve_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
guint16 flags;
|
||||
|
||||
/* Cursor state. */
|
||||
if (len < 2) {
|
||||
GST_WARNING_OBJECT (dec, "Cursor data too short");
|
||||
return -1;
|
||||
}
|
||||
|
||||
flags = RFB_GET_UINT16 (data);
|
||||
dec->cursor.visible = flags & 0x01;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int
|
||||
vmnc_handle_wmvf_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
/* Cursor position. */
|
||||
dec->cursor.x = rect->x;
|
||||
dec->cursor.y = rect->y;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
vmnc_handle_wmvg_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
/* Keyboard stuff; not interesting for playback */
|
||||
if (len < 10) {
|
||||
GST_WARNING_OBJECT (dec, "Keyboard data too short");
|
||||
return -1;
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
static int
|
||||
vmnc_handle_wmvh_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
/* More keyboard stuff; not interesting for playback */
|
||||
if (len < 4) {
|
||||
GST_WARNING_OBJECT (dec, "Keyboard data too short");
|
||||
return -1;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
static int
|
||||
vmnc_handle_wmvj_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
/* VM state info, not interesting for playback */
|
||||
if (len < 2) {
|
||||
GST_WARNING_OBJECT (dec, "VM state data too short");
|
||||
return -1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
static void
|
||||
render_raw_tile (GstVMncDec * dec, guint8 * data, int x, int y,
|
||||
int width, int height)
|
||||
{
|
||||
int i;
|
||||
guint8 *dst, *src;
|
||||
int line;
|
||||
|
||||
src = data;
|
||||
dst = dec->imagedata + dec->format.stride * y +
|
||||
dec->format.bytes_per_pixel * x;
|
||||
line = width * dec->format.bytes_per_pixel;
|
||||
|
||||
for (i = 0; i < height; i++) {
|
||||
/* This is wrong-endian currently */
|
||||
memcpy (dst, src, line);
|
||||
|
||||
dst += dec->format.stride;
|
||||
src += line;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
render_subrect (GstVMncDec * dec, int x, int y, int width,
|
||||
int height, guint32 colour)
|
||||
{
|
||||
/* Crazy inefficient! */
|
||||
int i, j;
|
||||
guint8 *dst;
|
||||
|
||||
for (i = 0; i < height; i++) {
|
||||
dst = dec->imagedata + dec->format.stride * (y + i) +
|
||||
dec->format.bytes_per_pixel * x;
|
||||
for (j = 0; j < width; j++) {
|
||||
memcpy (dst, &colour, dec->format.bytes_per_pixel);
|
||||
dst += dec->format.bytes_per_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
vmnc_handle_raw_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
int datalen = rect->width * rect->height * dec->format.bytes_per_pixel;
|
||||
|
||||
if (len < datalen) {
|
||||
GST_WARNING_OBJECT (dec, "Raw data too short");
|
||||
return -1;
|
||||
}
|
||||
|
||||
render_raw_tile (dec, data, rect->x, rect->y, rect->width, rect->height);
|
||||
|
||||
return datalen;
|
||||
}
|
||||
|
||||
#define READ_PIXEL(pixel, data, off, len) \
|
||||
if (dec->format.bytes_per_pixel == 1) { \
|
||||
if (off >= len) \
|
||||
return -1; \
|
||||
pixel = data[off++]; \
|
||||
} else if (dec->format.bytes_per_pixel == 2) { \
|
||||
if (off+2 > len) \
|
||||
return -1; \
|
||||
pixel = (*(guint16 *)(data + off)); \
|
||||
off += 2; \
|
||||
} else { \
|
||||
if (off+4 > len) \
|
||||
return -1; \
|
||||
pixel = (*(guint32 *)(data + off)); \
|
||||
off += 4; \
|
||||
}
|
||||
|
||||
static int
|
||||
vmnc_handle_hextile_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
|
||||
guint8 * data, int len)
|
||||
{
|
||||
int tilesx = GST_ROUND_UP_16 (rect->width) / 16;
|
||||
int tilesy = GST_ROUND_UP_16 (rect->height) / 16;
|
||||
int x, y, z;
|
||||
int off = 0;
|
||||
int subrects;
|
||||
int coloured;
|
||||
int width, height;
|
||||
guint32 fg = 0, bg = 0, colour;
|
||||
guint8 flags;
|
||||
|
||||
for (y = 0; y < tilesy; y++) {
|
||||
if (y == tilesy - 1)
|
||||
height = rect->height - (tilesy - 1) * 16;
|
||||
else
|
||||
height = 16;
|
||||
|
||||
for (x = 0; x < tilesx; x++) {
|
||||
if (x == tilesx - 1)
|
||||
width = rect->width - (tilesx - 1) * 16;
|
||||
else
|
||||
width = 16;
|
||||
|
||||
if (off >= len) {
|
||||
return -1;
|
||||
}
|
||||
flags = data[off++];
|
||||
|
||||
if (flags & 0x1) {
|
||||
if (off + width * height * dec->format.bytes_per_pixel > len) {
|
||||
return -1;
|
||||
}
|
||||
render_raw_tile (dec, data + off, rect->x + x * 16, rect->y + y * 16,
|
||||
width, height);
|
||||
off += width * height * dec->format.bytes_per_pixel;
|
||||
} else {
|
||||
if (flags & 0x2) {
|
||||
READ_PIXEL (bg, data, off, len)
|
||||
}
|
||||
if (flags & 0x4) {
|
||||
READ_PIXEL (fg, data, off, len)
|
||||
}
|
||||
|
||||
subrects = 0;
|
||||
if (flags & 0x8) {
|
||||
if (off >= len) {
|
||||
return -1;
|
||||
}
|
||||
subrects = data[off++];
|
||||
}
|
||||
|
||||
/* Paint background colour on entire tile */
|
||||
render_subrect (dec, rect->x + x * 16, rect->y + y * 16, width, height,
|
||||
bg);
|
||||
|
||||
coloured = flags & 0x10;
|
||||
for (z = 0; z < subrects; z++) {
|
||||
if (off + (coloured ? 3 : 2) > len)
|
||||
return -1;
|
||||
if (coloured) {
|
||||
READ_PIXEL (colour, data, off, len);
|
||||
} else
|
||||
colour = fg;
|
||||
|
||||
{
|
||||
int off_x = (data[off] & 0xf0) >> 4;
|
||||
int off_y = (data[off] & 0x0f);
|
||||
int w = ((data[off + 1] & 0xf0) >> 4) + 1;
|
||||
int h = (data[off + 1] & 0x0f) + 1;
|
||||
|
||||
off += 2;
|
||||
|
||||
/* Ensure we don't have out of bounds coordinates */
|
||||
if (off_x + w > width || off_y + h > height)
|
||||
return -1;
|
||||
|
||||
render_subrect (dec, rect->x + x * 16 + off_x,
|
||||
rect->y + y * 16 + off_y, w, h, colour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return off;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
vmnc_handle_packet (GstVMncDec * dec, GstBuffer * buf)
|
||||
{
|
||||
guint8 *data = GST_BUFFER_DATA (buf);
|
||||
int len = GST_BUFFER_SIZE (buf);
|
||||
int type;
|
||||
|
||||
if (len < 4) {
|
||||
GST_WARNING_OBJECT (dec, "Packet too short");
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
type = data[0];
|
||||
|
||||
switch (type) {
|
||||
case 0:
|
||||
{
|
||||
int numrect = RFB_GET_UINT16 (data + 2);
|
||||
int i;
|
||||
int offset = 4;
|
||||
int read;
|
||||
|
||||
for (i = 0; i < numrect; i++) {
|
||||
struct RfbRectangle r;
|
||||
rectangle_handler handler;
|
||||
|
||||
if (len < offset + 12) {
|
||||
GST_WARNING_OBJECT (dec, "Packet too short for rectangle header");
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
r.x = RFB_GET_UINT16 (data + offset);
|
||||
r.y = RFB_GET_UINT16 (data + offset + 2);
|
||||
r.width = RFB_GET_UINT16 (data + offset + 4);
|
||||
r.height = RFB_GET_UINT16 (data + offset + 6);
|
||||
r.type = RFB_GET_UINT32 (data + offset + 8);
|
||||
|
||||
if (r.type != TYPE_WMVi) {
|
||||
/* We must have a WMVi packet to initialise things before we can
|
||||
* continue */
|
||||
if (!dec->caps) {
|
||||
GST_WARNING_OBJECT (dec, "Received packet without WMVi");
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
if (r.x + r.width > dec->format.width ||
|
||||
r.y + r.height > dec->format.height) {
|
||||
GST_WARNING_OBJECT (dec, "Rectangle out of range, type %d", r.type);
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
switch (r.type) {
|
||||
case TYPE_WMVd:
|
||||
handler = vmnc_handle_wmvd_rectangle;
|
||||
break;
|
||||
case TYPE_WMVe:
|
||||
handler = vmnc_handle_wmve_rectangle;
|
||||
break;
|
||||
case TYPE_WMVf:
|
||||
handler = vmnc_handle_wmvf_rectangle;
|
||||
break;
|
||||
case TYPE_WMVg:
|
||||
handler = vmnc_handle_wmvg_rectangle;
|
||||
break;
|
||||
case TYPE_WMVh:
|
||||
handler = vmnc_handle_wmvh_rectangle;
|
||||
break;
|
||||
case TYPE_WMVi:
|
||||
handler = vmnc_handle_wmvi_rectangle;
|
||||
break;
|
||||
case TYPE_WMVj:
|
||||
handler = vmnc_handle_wmvj_rectangle;
|
||||
break;
|
||||
case TYPE_RAW:
|
||||
handler = vmnc_handle_raw_rectangle;
|
||||
break;
|
||||
case TYPE_HEXTILE:
|
||||
handler = vmnc_handle_hextile_rectangle;
|
||||
break;
|
||||
default:
|
||||
GST_WARNING_OBJECT (dec, "Unknown rectangle type");
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
read = handler (dec, &r, data + offset + 12, len - offset - 12);
|
||||
if (read < 0) {
|
||||
GST_WARNING_OBJECT (dec, "Error calling rectangle handler\n");
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
offset += 12 + read;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
GST_WARNING_OBJECT (dec, "Packet type unknown: %d", type);
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
vmnc_dec_setcaps (GstPad * pad, GstCaps * caps)
|
||||
{
|
||||
/* We require a format descriptor in-stream, so we ignore the info from the
|
||||
* container here. We just use the framerate */
|
||||
GstVMncDec *dec = GST_VMNC_DEC (gst_pad_get_parent (pad));
|
||||
GstStructure *structure = gst_caps_get_structure (caps, 0);
|
||||
|
||||
/* We gave these a default in reset(), so we don't need to check for failure
|
||||
* here */
|
||||
gst_structure_get_fraction (structure, "framerate",
|
||||
&dec->framerate_num, &dec->framerate_denom);
|
||||
|
||||
gst_object_unref (dec);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
vmnc_dec_chain (GstPad * pad, GstBuffer * buf)
|
||||
{
|
||||
GstVMncDec *dec;
|
||||
GstFlowReturn res;
|
||||
GstBuffer *outbuf;
|
||||
|
||||
dec = GST_VMNC_DEC (gst_pad_get_parent (pad));
|
||||
|
||||
res = vmnc_handle_packet (dec, buf);
|
||||
|
||||
if (res == GST_FLOW_OK) {
|
||||
outbuf = vmnc_make_buffer (dec, buf);
|
||||
res = gst_pad_push (dec->srcpad, outbuf);
|
||||
}
|
||||
gst_buffer_unref (buf);
|
||||
|
||||
gst_object_unref (dec);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
vmnc_dec_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstVMncDec *dec = GST_VMNC_DEC (element);
|
||||
GstStateChangeReturn ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_NULL_TO_READY:
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
gst_vmnc_dec_reset (dec);
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = parent_class->change_state (element, transition);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
gst_vmnc_dec_reset (dec);
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
vmnc_dec_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
/*GstVMncDec *dec = GST_VMNC_DEC (object); */
|
||||
|
||||
switch (prop_id) {
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
vmnc_dec_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
/*GstVMncDec *dec = GST_VMNC_DEC (object); */
|
||||
|
||||
switch (prop_id) {
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
if (!gst_element_register (plugin, "vmncdec", GST_RANK_PRIMARY,
|
||||
gst_vmnc_dec_get_type ()))
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
"vmnc",
|
||||
"VMnc video plugin library",
|
||||
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
Loading…
Reference in a new issue