From 1a428b03ed3ec854d9384b26dcdb8f1aba15a585 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 3 Mar 2007 22:13:22 +0000 Subject: 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 --- gst/vmnc/Makefile.am | 6 + gst/vmnc/vmncdec.c | 958 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 964 insertions(+) create mode 100644 gst/vmnc/Makefile.am create mode 100644 gst/vmnc/vmncdec.c (limited to 'gst/vmnc') diff --git a/gst/vmnc/Makefile.am b/gst/vmnc/Makefile.am new file mode 100644 index 00000000..4c0fb328 --- /dev/null +++ b/gst/vmnc/Makefile.am @@ -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) diff --git a/gst/vmnc/vmncdec.c b/gst/vmnc/vmncdec.c new file mode 100644 index 00000000..7dd0b1c4 --- /dev/null +++ b/gst/vmnc/vmncdec.c @@ -0,0 +1,958 @@ +/* GStreamer + * Copyright (C) 2007 Michael Smith + * + * 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 +#include +#include + +#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 "); + +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) -- cgit v1.2.1