/* 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 #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) GST_READ_UINT32_BE(ptr) #define RFB_GET_UINT16(ptr) GST_READ_UINT16_BE(ptr) #define RFB_GET_UINT8(ptr) GST_READ_UINT8(ptr) enum { ARG_0, }; enum { ERROR_INVALID = -1, /* Invalid data in bitstream */ ERROR_INSUFFICIENT_DATA = -2 /* Haven't received enough data yet */ }; #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; gboolean have_format; int parsed; GstAdapter *adapter; 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 vmnc_dec_finalize (GObject * object); 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; gobject_class->finalize = vmnc_dec_finalize; 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); dec->adapter = gst_adapter_new (); } static void vmnc_dec_finalize (GObject * object) { GstVMncDec *dec = GST_VMNC_DEC (object); g_object_unref (dec->adapter); G_OBJECT_CLASS (parent_class)->finalize (object); } 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; dec->have_format = FALSE; gst_adapter_clear (dec->adapter); } 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, const guint8 * data, int len, gboolean decode); static int vmnc_handle_wmvi_rectangle (GstVMncDec * dec, struct RfbRectangle *rect, const guint8 * data, int len, gboolean decode) { GstCaps *caps; gint bpp, tc; guint32 redmask, greenmask, bluemask; guint32 endianness, dataendianness; /* A WMVi rectangle has a 16byte payload */ if (len < 16) { GST_DEBUG_OBJECT (dec, "Bad WMVi rect: too short"); return ERROR_INSUFFICIENT_DATA; } /* We only compare 13 bytes; ignoring the 3 padding bytes at the end */ if (dec->caps && 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 ERROR_INVALID; } 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 ERROR_INVALID; } if (!tc) { GST_WARNING_OBJECT (dec, "Paletted video not supported"); return ERROR_INVALID; } 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); } } dec->have_format = TRUE; if (!decode) { GST_DEBUG_OBJECT (dec, "Parsing, not setting caps"); return 16; } 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); GST_DEBUG_OBJECT (dec, "Allocated image data at %p", dec->imagedata); 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); } if (inbuf) { gst_buffer_copy_metadata (buf, inbuf, GST_BUFFER_COPY_TIMESTAMPS); } gst_buffer_set_caps (buf, dec->caps); return buf; } static int vmnc_handle_wmvd_rectangle (GstVMncDec * dec, struct RfbRectangle *rect, const guint8 * data, int len, gboolean decode) { /* Cursor data. */ int datalen = 2; int type, size; if (len < datalen) { GST_DEBUG_OBJECT (dec, "Cursor data too short"); return ERROR_INSUFFICIENT_DATA; } 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 ERROR_INVALID; } if (len < datalen) { GST_DEBUG_OBJECT (dec, "Cursor data too short"); return ERROR_INSUFFICIENT_DATA; } else if (!decode) return datalen; 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, const guint8 * data, int len, gboolean decode) { guint16 flags; /* Cursor state. */ if (len < 2) { GST_DEBUG_OBJECT (dec, "Cursor data too short"); return ERROR_INSUFFICIENT_DATA; } else if (!decode) return 2; flags = RFB_GET_UINT16 (data); dec->cursor.visible = flags & 0x01; return 2; } static int vmnc_handle_wmvf_rectangle (GstVMncDec * dec, struct RfbRectangle *rect, const guint8 * data, int len, gboolean decode) { /* 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, const guint8 * data, int len, gboolean decode) { /* Keyboard stuff; not interesting for playback */ if (len < 10) { GST_DEBUG_OBJECT (dec, "Keyboard data too short"); return ERROR_INSUFFICIENT_DATA; } return 10; } static int vmnc_handle_wmvh_rectangle (GstVMncDec * dec, struct RfbRectangle *rect, const guint8 * data, int len, gboolean decode) { /* More keyboard stuff; not interesting for playback */ if (len < 4) { GST_DEBUG_OBJECT (dec, "Keyboard data too short"); return ERROR_INSUFFICIENT_DATA; } return 4; } static int vmnc_handle_wmvj_rectangle (GstVMncDec * dec, struct RfbRectangle *rect, const guint8 * data, int len, gboolean decode) { /* VM state info, not interesting for playback */ if (len < 2) { GST_DEBUG_OBJECT (dec, "VM state data too short"); return ERROR_INSUFFICIENT_DATA; } return 2; } static void render_raw_tile (GstVMncDec * dec, const guint8 * data, int x, int y, int width, int height) { int i; guint8 *dst; const guint8 *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, const guint8 * data, int len, gboolean decode) { int datalen = rect->width * rect->height * dec->format.bytes_per_pixel; if (len < datalen) { GST_DEBUG_OBJECT (dec, "Raw data too short"); return ERROR_INSUFFICIENT_DATA; } if (decode) render_raw_tile (dec, data, rect->x, rect->y, rect->width, rect->height); return datalen; } static int vmnc_handle_copy_rectangle (GstVMncDec * dec, struct RfbRectangle *rect, const guint8 * data, int len, gboolean decode) { int src_x, src_y; guint8 *src, *dst; int i; if (len < 4) { GST_DEBUG_OBJECT (dec, "Copy data too short"); return ERROR_INSUFFICIENT_DATA; } else if (!decode) return 4; src_x = RFB_GET_UINT16 (data); src_y = RFB_GET_UINT16 (data + 2); /* Our destination rectangle is guaranteed in-frame; check this for the source * rectangle. */ if (src_x + rect->width > dec->format.width || src_y + rect->height > dec->format.height) { GST_WARNING_OBJECT (dec, "Source rectangle out of range"); return ERROR_INVALID; } if (src_y > rect->y || src_x > rect->x) { /* Moving forward */ src = dec->imagedata + dec->format.stride * src_y + dec->format.bytes_per_pixel * src_x; dst = dec->imagedata + dec->format.stride * rect->y + dec->format.bytes_per_pixel * rect->x; for (i = 0; i < rect->height; i++) { memmove (dst, src, rect->width * dec->format.bytes_per_pixel); dst += dec->format.stride; src += dec->format.stride; } } else { /* Backwards */ src = dec->imagedata + dec->format.stride * (src_y + rect->height - 1) + dec->format.bytes_per_pixel * src_x; dst = dec->imagedata + dec->format.stride * (rect->y + rect->height - 1) + dec->format.bytes_per_pixel * rect->x; for (i = rect->height; i > 0; i--) { memmove (dst, src, rect->width * dec->format.bytes_per_pixel); dst -= dec->format.stride; src -= dec->format.stride; } } return 4; } /* FIXME: data+off might not be properly aligned */ #define READ_PIXEL(pixel, data, off, len) \ if (dec->format.bytes_per_pixel == 1) { \ if (off >= len) \ return ERROR_INSUFFICIENT_DATA; \ pixel = data[off++]; \ } else if (dec->format.bytes_per_pixel == 2) { \ if (off+2 > len) \ return ERROR_INSUFFICIENT_DATA; \ pixel = (*(guint16 *)(data + off)); \ off += 2; \ } else { \ if (off+4 > len) \ return ERROR_INSUFFICIENT_DATA; \ pixel = (*(guint32 *)(data + off)); \ off += 4; \ } static int vmnc_handle_hextile_rectangle (GstVMncDec * dec, struct RfbRectangle *rect, const guint8 * data, int len, gboolean decode) { 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 ERROR_INSUFFICIENT_DATA; } flags = data[off++]; if (flags & 0x1) { if (off + width * height * dec->format.bytes_per_pixel > len) { return ERROR_INSUFFICIENT_DATA; } if (decode) 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 ERROR_INSUFFICIENT_DATA; } subrects = data[off++]; } /* Paint background colour on entire tile */ if (decode) render_subrect (dec, rect->x + x * 16, rect->y + y * 16, width, height, bg); coloured = flags & 0x10; for (z = 0; z < subrects; z++) { if (coloured) { READ_PIXEL (colour, data, off, len); } else colour = fg; if (off + 2 > len) return ERROR_INSUFFICIENT_DATA; { 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) { GST_WARNING_OBJECT (dec, "Subrect out of bounds: %d-%d x %d-%d " "extends outside %dx%d", off_x, w, off_y, h, width, height); return ERROR_INVALID; } if (decode) render_subrect (dec, rect->x + x * 16 + off_x, rect->y + y * 16 + off_y, w, h, colour); } } } } } return off; } /* Handle a packet in one of two modes: decode or parse. * In parse mode, we don't execute any of the decoding, we just do enough to * figure out how many bytes it contains * * Returns: >= 0, the number of bytes consumed * < 0, packet too short to decode, or error */ static int vmnc_handle_packet (GstVMncDec * dec, const guint8 * data, int len, gboolean decode) { int type; int offset = 0; if (len < 4) { GST_DEBUG_OBJECT (dec, "Packet too short"); return ERROR_INSUFFICIENT_DATA; } type = data[0]; switch (type) { case 0: { int numrect = RFB_GET_UINT16 (data + 2); int i; int read; offset = 4; for (i = 0; i < numrect; i++) { struct RfbRectangle r; rectangle_handler handler; if (len < offset + 12) { GST_DEBUG_OBJECT (dec, "Packet too short for rectangle header: %d < %d", len, offset + 12); return ERROR_INSUFFICIENT_DATA; } GST_DEBUG_OBJECT (dec, "Reading rectangle %d", i); 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->have_format) { GST_WARNING_OBJECT (dec, "Received packet without WMVi: %d", r.type); return ERROR_INVALID; } 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 ERROR_INVALID; } } 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_COPY: handler = vmnc_handle_copy_rectangle; break; case TYPE_HEXTILE: handler = vmnc_handle_hextile_rectangle; break; default: GST_WARNING_OBJECT (dec, "Unknown rectangle type"); return ERROR_INVALID; } read = handler (dec, &r, data + offset + 12, len - offset - 12, decode); if (read < 0) { GST_DEBUG_OBJECT (dec, "Error calling rectangle handler\n"); return read; } offset += 12 + read; } break; } default: GST_WARNING_OBJECT (dec, "Packet type unknown: %d", type); return ERROR_INVALID; } return offset; } 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)); if (gst_caps_get_size (caps) > 0) { 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); dec->parsed = TRUE; } else { GST_DEBUG_OBJECT (dec, "Unparsed input"); dec->parsed = FALSE; } gst_object_unref (dec); return TRUE; } static GstFlowReturn vmnc_dec_chain_frame (GstVMncDec * dec, GstBuffer * inbuf, const guint8 * data, int len) { int res; GstFlowReturn ret = GST_FLOW_OK; GstBuffer *outbuf; res = vmnc_handle_packet (dec, data, len, TRUE); if (res < 0) { ret = GST_FLOW_ERROR; GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("Couldn't decode packet")); } else { GST_DEBUG_OBJECT (dec, "read %d bytes of %d", res, len); /* inbuf may be NULL; that's ok */ outbuf = vmnc_make_buffer (dec, inbuf); ret = gst_pad_push (dec->srcpad, outbuf); } return ret; } static GstFlowReturn vmnc_dec_chain (GstPad * pad, GstBuffer * buf) { GstVMncDec *dec; GstFlowReturn ret = GST_FLOW_OK; dec = GST_VMNC_DEC (gst_pad_get_parent (pad)); if (!dec->parsed) { /* Submit our input buffer to adapter, then parse. Push each frame found * through the decoder */ int avail; const guint8 *data; int read = 0; gst_adapter_push (dec->adapter, buf); avail = gst_adapter_available (dec->adapter); data = gst_adapter_peek (dec->adapter, avail); GST_DEBUG_OBJECT (dec, "Parsing %d bytes", avail); while (TRUE) { int len = vmnc_handle_packet (dec, data, avail, FALSE); if (len == ERROR_INSUFFICIENT_DATA) { GST_DEBUG_OBJECT (dec, "Not enough data yet"); ret = GST_FLOW_OK; break; } else if (len < 0) { GST_DEBUG_OBJECT (dec, "Fatal error in bitstream"); ret = GST_FLOW_ERROR; break; } GST_DEBUG_OBJECT (dec, "Parsed packet: %d bytes", len); ret = vmnc_dec_chain_frame (dec, NULL, data, len); avail -= len; data += len; read += len; if (ret != GST_FLOW_OK) break; } GST_DEBUG_OBJECT (dec, "Flushing %d bytes", read); gst_adapter_flush (dec->adapter, read); } else { ret = vmnc_dec_chain_frame (dec, buf, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); gst_buffer_unref (buf); } gst_object_unref (dec); return ret; } 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)