/* GStreamer
 * Copyright (C) 2005 Sebastien Moutte <sebastien@moutte.net>
 * Copyright (C) 2007 Pioneers of the Inevitable <songbird@songbirdnest.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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * The development of this code was made possible due to the involvement
 * of Pioneers of the Inevitable, the creators of the Songbird Music player
 *
 */

/**
 * SECTION:element-directdrawsink
 *
 * DirectdrawSink renders video RGB frames to any win32 window. This element
 * can receive a window ID from the application through the #XOverlay interface
 * and will then render video frames in this window.
 * If no Window ID was provided by the application, the element will create its
 * own internal window and render into it.
 *
 * <refsect2>
 * <title>Example pipelines</title>
 * |[
 * gst-launch -v videotestsrc ! directdrawsink
 * ]| a simple pipeline to test the sink
 * </refsect2>
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gstdirectdrawsink.h"

GST_DEBUG_CATEGORY_STATIC (directdrawsink_debug);
#define GST_CAT_DEFAULT directdrawsink_debug

static void gst_directdraw_sink_init_interfaces (GType type);

GST_BOILERPLATE_FULL (GstDirectDrawSink, gst_directdraw_sink, GstVideoSink,
    GST_TYPE_VIDEO_SINK, gst_directdraw_sink_init_interfaces);

static void gst_directdraw_sink_finalize (GObject * object);
static void gst_directdraw_sink_set_property (GObject * object,
    guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_directdraw_sink_get_property (GObject * object,
    guint prop_id, GValue * value, GParamSpec * pspec);
static GstCaps *gst_directdraw_sink_get_caps (GstBaseSink * bsink);
static gboolean gst_directdraw_sink_set_caps (GstBaseSink * bsink,
    GstCaps * caps);
static GstStateChangeReturn gst_directdraw_sink_change_state (GstElement *
    element, GstStateChange transition);
static GstFlowReturn gst_directdraw_sink_buffer_alloc (GstBaseSink * bsink,
    guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf);
static void gst_directdraw_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
    GstClockTime * start, GstClockTime * end);
static GstFlowReturn gst_directdraw_sink_show_frame (GstBaseSink * bsink,
    GstBuffer * buf);

/* utils */
static gboolean gst_directdraw_sink_setup_ddraw (GstDirectDrawSink * ddrawsink);
static gboolean gst_directdraw_sink_create_default_window (GstDirectDrawSink *
    ddrawsink);
static gboolean gst_directdraw_sink_check_primary_surface (GstDirectDrawSink *
    ddrawsink);
static gboolean gst_directdraw_sink_check_offscreen_surface (GstDirectDrawSink *
    ddrawsink);
static GstCaps *gst_directdraw_sink_get_ddrawcaps (GstDirectDrawSink *
    ddrawsink);
static GstCaps
    * gst_directdraw_sink_create_caps_from_surfacedesc (LPDDSURFACEDESC2 desc);
static void gst_directdraw_sink_cleanup (GstDirectDrawSink * ddrawsink);
static void gst_directdraw_sink_bufferpool_clear (GstDirectDrawSink *
    ddrawsink);
static int gst_directdraw_sink_get_depth (LPDDPIXELFORMAT lpddpfPixelFormat);
static gboolean gst_ddrawvideosink_get_format_from_caps (GstDirectDrawSink *
    ddrawsink, GstCaps * caps, DDPIXELFORMAT * pPixelFormat);
static void gst_directdraw_sink_center_rect (GstDirectDrawSink * ddrawsink,
    RECT src, RECT dst, RECT * result);
static const char *DDErrorString (HRESULT hr);

/* surfaces management functions */
static void gst_directdraw_sink_surface_destroy (GstDirectDrawSink * ddrawsink,
    GstDDrawSurface * surface);
static GstDDrawSurface *gst_directdraw_sink_surface_create (GstDirectDrawSink *
    ddrawsink, GstCaps * caps, size_t size);
static gboolean gst_directdraw_sink_surface_check (GstDirectDrawSink *
    ddrawsink, GstDDrawSurface * surface);

static GstStaticPadTemplate directdrawsink_sink_factory =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-raw-rgb, "
        "framerate = (fraction) [ 0, MAX ], "
        "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
    );

enum
{
  PROP_0,
  PROP_KEEP_ASPECT_RATIO
};

/* XOverlay interface implementation */
static gboolean
gst_directdraw_sink_interface_supported (GstImplementsInterface * iface,
    GType type)
{
  if (type == GST_TYPE_X_OVERLAY)
    return TRUE;
  else if (type == GST_TYPE_NAVIGATION)
    return TRUE;
  return FALSE;
}

static void
gst_directdraw_sink_interface_init (GstImplementsInterfaceClass * klass)
{
  klass->supported = gst_directdraw_sink_interface_supported;
}

static void
gst_directdraw_sink_set_window_handle (GstXOverlay * overlay,
    guintptr window_handle)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (overlay);

  GST_OBJECT_LOCK (ddrawsink);
  /* check if we are already using this window id */
  if (ddrawsink->video_window == (HWND) window_handle) {
    GST_OBJECT_UNLOCK (ddrawsink);
    return;
  }

  if (window_handle) {
    HRESULT hres;

    /* If we had an internal window, close it first */
    if (ddrawsink->video_window && ddrawsink->our_video_window) {
      /* Trick to let the event thread know that it has to die silently */
      ddrawsink->our_video_window = FALSE;
      /* Post quit message and wait for our event window thread */
      PostMessage (ddrawsink->video_window, WM_QUIT, 0, 0);
    }

    ddrawsink->video_window = (HWND) window_handle;
    ddrawsink->our_video_window = FALSE;
    if (ddrawsink->setup) {
      /* update the clipper object with the new window */
      hres = IDirectDrawClipper_SetHWnd (ddrawsink->clipper, 0,
          ddrawsink->video_window);
    }
  }
  /* FIXME: Handle the case where window_handle is 0 and we want the sink to
   * create a new window when playback was already started (after set_caps) */
  GST_OBJECT_UNLOCK (ddrawsink);
}

static void
gst_directdraw_sink_expose (GstXOverlay * overlay)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (overlay);

  gst_directdraw_sink_show_frame (GST_BASE_SINK (ddrawsink), NULL);
}

static void
gst_directdraw_sink_xoverlay_interface_init (GstXOverlayClass * iface)
{
  iface->set_window_handle = gst_directdraw_sink_set_window_handle;
  iface->expose = gst_directdraw_sink_expose;
}

static void
gst_directdraw_sink_navigation_send_event (GstNavigation * navigation,
    GstStructure * structure)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (navigation);
  GstEvent *event;
  GstVideoRectangle src, dst, result;
  double x, y, old_x, old_y;
  GstPad *pad = NULL;

  src.w = GST_VIDEO_SINK_WIDTH (ddrawsink);
  src.h = GST_VIDEO_SINK_HEIGHT (ddrawsink);
  dst.w = ddrawsink->out_width;
  dst.h = ddrawsink->out_height;
  gst_video_sink_center_rect (src, dst, &result, FALSE);

  event = gst_event_new_navigation (structure);

  /* Our coordinates can be wrong here if we centered the video */

  /* Converting pointer coordinates to the non scaled geometry */
  if (gst_structure_get_double (structure, "pointer_x", &old_x)) {
    x = old_x;

    if (x >= result.x && x <= (result.x + result.w)) {
      x -= result.x;
      x *= ddrawsink->video_width;
      x /= result.w;
    } else {
      x = 0;
    }
    GST_DEBUG_OBJECT (ddrawsink, "translated navigation event x "
        "coordinate from %f to %f", old_x, x);
    gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
  }
  if (gst_structure_get_double (structure, "pointer_y", &old_y)) {
    y = old_y;

    if (y >= result.y && y <= (result.y + result.h)) {
      y -= result.y;
      y *= ddrawsink->video_height;
      y /= result.h;
    } else {
      y = 0;
    }
    GST_DEBUG_OBJECT (ddrawsink, "translated navigation event y "
        "coordinate from %f to %f", old_y, y);
    gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
  }

  pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (ddrawsink));

  if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
    gst_pad_send_event (pad, event);

    gst_object_unref (pad);
  }
}

static void
gst_directdraw_sink_navigation_interface_init (GstNavigationInterface * iface)
{
  iface->send_event = gst_directdraw_sink_navigation_send_event;
}

static void
gst_directdraw_sink_init_interfaces (GType type)
{
  static const GInterfaceInfo iface_info = {
    (GInterfaceInitFunc) gst_directdraw_sink_interface_init,
    NULL,
    NULL,
  };

  static const GInterfaceInfo xoverlay_info = {
    (GInterfaceInitFunc) gst_directdraw_sink_xoverlay_interface_init,
    NULL,
    NULL,
  };

  static const GInterfaceInfo navigation_info = {
    (GInterfaceInitFunc) gst_directdraw_sink_navigation_interface_init,
    NULL,
    NULL,
  };

  g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE,
      &iface_info);
  g_type_add_interface_static (type, GST_TYPE_X_OVERLAY, &xoverlay_info);
  g_type_add_interface_static (type, GST_TYPE_NAVIGATION, &navigation_info);
}

/* Subclass of GstBuffer which manages buffer_pool surfaces lifetime    */
static void gst_ddrawsurface_finalize (GstMiniObject * mini_object);
static GstBufferClass *ddrawsurface_parent_class = NULL;

static void
gst_ddrawsurface_init (GstDDrawSurface * surface, gpointer g_class)
{
  surface->surface = NULL;
  surface->width = 0;
  surface->height = 0;
  surface->ddrawsink = NULL;
  surface->locked = FALSE;
  surface->system_memory = FALSE;
  memset (&surface->dd_pixel_format, 0, sizeof (DDPIXELFORMAT));
}

static void
gst_ddrawsurface_class_init (gpointer g_class, gpointer class_data)
{
  GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS (g_class);

  ddrawsurface_parent_class = g_type_class_peek_parent (g_class);

  mini_object_class->finalize = GST_DEBUG_FUNCPTR (gst_ddrawsurface_finalize);
}

static GType
gst_ddrawsurface_get_type (void)
{
  static GType _gst_ddrawsurface_type;

  if (G_UNLIKELY (_gst_ddrawsurface_type == 0)) {
    static const GTypeInfo ddrawsurface_info = {
      sizeof (GstBufferClass),
      NULL,
      NULL,
      gst_ddrawsurface_class_init,
      NULL,
      NULL,
      sizeof (GstDDrawSurface),
      0,
      (GInstanceInitFunc) gst_ddrawsurface_init,
      NULL
    };
    _gst_ddrawsurface_type = g_type_register_static (GST_TYPE_BUFFER,
        "GstDDrawSurface", &ddrawsurface_info, 0);
  }
  return _gst_ddrawsurface_type;
}

static void
gst_ddrawsurface_finalize (GstMiniObject * mini_object)
{
  GstDirectDrawSink *ddrawsink = NULL;
  GstDDrawSurface *surface;

  surface = (GstDDrawSurface *) mini_object;

  ddrawsink = surface->ddrawsink;
  if (!ddrawsink)
    goto no_sink;

  /* If our geometry changed we can't reuse that image. */
  if ((surface->width != ddrawsink->video_width) ||
      (surface->height != ddrawsink->video_height) ||
      (memcmp (&surface->dd_pixel_format, &ddrawsink->dd_pixel_format,
              sizeof (DDPIXELFORMAT)) != 0 ||
          !gst_directdraw_sink_surface_check (ddrawsink, surface))
      ) {
    GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
        "destroy image as its size changed %dx%d vs current %dx%d",
        surface->width, surface->height, ddrawsink->video_width,
        ddrawsink->video_height);
    gst_directdraw_sink_surface_destroy (ddrawsink, surface);
    GST_MINI_OBJECT_CLASS (ddrawsurface_parent_class)->finalize (mini_object);
  } else {
    /* In that case we can reuse the image and add it to our image pool. */
    GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
        "recycling image in pool");

    /* need to increment the refcount again to recycle */
    gst_buffer_ref (GST_BUFFER (surface));

    g_mutex_lock (ddrawsink->pool_lock);
    ddrawsink->buffer_pool = g_slist_prepend (ddrawsink->buffer_pool, surface);
    g_mutex_unlock (ddrawsink->pool_lock);
  }

  return;

no_sink:
  GST_CAT_WARNING (directdrawsink_debug, "no sink found");
  GST_MINI_OBJECT_CLASS (ddrawsurface_parent_class)->finalize (mini_object);
  return;
}

/************************************************************************/
/* Directdraw sink functions                                            */
/************************************************************************/
static void
gst_directdraw_sink_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_set_details_simple (element_class, "Direct Draw Video Sink",
      "Sink/Video",
      "Output to a video card via Direct Draw",
      "Sebastien Moutte <sebastien@moutte.net>");
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&directdrawsink_sink_factory));
}

static void
gst_directdraw_sink_class_init (GstDirectDrawSinkClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
  GstBaseSinkClass *gstbasesink_class;

  gobject_class = (GObjectClass *) klass;
  gstbasesink_class = (GstBaseSinkClass *) klass;
  gstelement_class = (GstElementClass *) klass;

  GST_DEBUG_CATEGORY_INIT (directdrawsink_debug, "directdrawsink", 0,
      "Directdraw sink");

  gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_directdraw_sink_finalize);
  gobject_class->get_property =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_get_property);
  gobject_class->set_property =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_set_property);
  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_change_state);
  gstbasesink_class->get_caps =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_get_caps);
  gstbasesink_class->set_caps =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_set_caps);
  gstbasesink_class->preroll =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_show_frame);
  gstbasesink_class->render =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_show_frame);
  gstbasesink_class->get_times =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_get_times);
  gstbasesink_class->buffer_alloc =
      GST_DEBUG_FUNCPTR (gst_directdraw_sink_buffer_alloc);

  /* install properties */
  /* setup aspect ratio mode */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
      PROP_KEEP_ASPECT_RATIO, g_param_spec_boolean ("force-aspect-ratio",
          "Force aspect ratio",
          "When enabled, scaling will respect original aspect ratio", FALSE,
          G_PARAM_READWRITE));
}

static void
gst_directdraw_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (object);

  switch (prop_id) {
    case PROP_KEEP_ASPECT_RATIO:
      ddrawsink->keep_aspect_ratio = g_value_get_boolean (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_directdraw_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (object);

  switch (prop_id) {
    case PROP_KEEP_ASPECT_RATIO:
      g_value_set_boolean (value, ddrawsink->keep_aspect_ratio);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_directdraw_sink_finalize (GObject * object)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (object);

  if (ddrawsink->pool_lock) {
    g_mutex_free (ddrawsink->pool_lock);
    ddrawsink->pool_lock = NULL;
  }
  if (ddrawsink->caps) {
    gst_caps_unref (ddrawsink->caps);
    ddrawsink->caps = NULL;
  }
  if (ddrawsink->setup) {
    gst_directdraw_sink_cleanup (ddrawsink);
  }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_directdraw_sink_init (GstDirectDrawSink * ddrawsink,
    GstDirectDrawSinkClass * g_class)
{
  /*init members variables */
  ddrawsink->ddraw_object = NULL;
  ddrawsink->primary_surface = NULL;
  ddrawsink->offscreen_surface = NULL;
  ddrawsink->clipper = NULL;
  ddrawsink->video_window = NULL;
  ddrawsink->our_video_window = TRUE;
  ddrawsink->last_buffer = NULL;
  ddrawsink->caps = NULL;
  ddrawsink->window_thread = NULL;
  ddrawsink->setup = FALSE;
  ddrawsink->buffer_pool = NULL;
  ddrawsink->keep_aspect_ratio = FALSE;
  ddrawsink->pool_lock = g_mutex_new ();
  ddrawsink->can_blit_between_colorspace = TRUE;
  ddrawsink->must_recreate_offscreen = FALSE;
  memset (&ddrawsink->dd_pixel_format, 0, sizeof (DDPIXELFORMAT));

  /*video default values */
  ddrawsink->video_height = 0;
  ddrawsink->video_width = 0;
  ddrawsink->fps_n = 0;
  ddrawsink->fps_d = 0;
}

static GstCaps *
gst_directdraw_sink_get_caps (GstBaseSink * bsink)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (bsink);
  GstCaps *caps = NULL;

  if (!ddrawsink->setup) {
    caps = gst_caps_copy (gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD
            (ddrawsink)));
    GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
        "getcaps called and we are not setup yet, " "returning template %"
        GST_PTR_FORMAT, caps);
  } else {
    caps = gst_caps_ref (ddrawsink->caps);
  }

  return caps;
}

static gboolean
gst_directdraw_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (bsink);
  GstStructure *structure = NULL;
  gboolean ret;
  const GValue *fps;

  structure = gst_caps_get_structure (caps, 0);
  if (!structure)
    return FALSE;

  ret = gst_structure_get_int (structure, "width", &ddrawsink->video_width);
  ret &= gst_structure_get_int (structure, "height", &ddrawsink->video_height);
  fps = gst_structure_get_value (structure, "framerate");
  ret &= (fps != NULL);
  ret &=
      gst_ddrawvideosink_get_format_from_caps (ddrawsink, caps,
      &ddrawsink->dd_pixel_format);
  if (!ret) {
    GST_ELEMENT_ERROR (ddrawsink, CORE, NEGOTIATION,
        ("Failed to get caps properties from caps"), (NULL));
    return FALSE;
  }
  GST_VIDEO_SINK_WIDTH (ddrawsink) = ddrawsink->video_width;
  GST_VIDEO_SINK_HEIGHT (ddrawsink) = ddrawsink->video_height;

  ddrawsink->fps_n = gst_value_get_fraction_numerator (fps);
  ddrawsink->fps_d = gst_value_get_fraction_denominator (fps);

  /* Notify application to set window id now */
  if (!ddrawsink->video_window) {
    gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (ddrawsink));
  }

  /* If we still don't have a window at that stage we create our own */
  if (!ddrawsink->video_window) {
    gst_directdraw_sink_create_default_window (ddrawsink);
  }

  /* if we are rendering to our own window, resize it to video size */
  if (ddrawsink->video_window && ddrawsink->our_video_window) {
    SetWindowPos (ddrawsink->video_window, NULL,
        0, 0, ddrawsink->video_width + (GetSystemMetrics (SM_CXSIZEFRAME) * 2),
        ddrawsink->video_height + GetSystemMetrics (SM_CYCAPTION) +
        (GetSystemMetrics (SM_CYSIZEFRAME) * 2), SWP_SHOWWINDOW | SWP_NOMOVE);
  }

  /* release the surface, we have to recreate it! */
  if (ddrawsink->offscreen_surface) {
    IDirectDrawSurface7_Release (ddrawsink->offscreen_surface);
    ddrawsink->offscreen_surface = NULL;
  }

  /* create an offscreen surface with the caps */
  ret = gst_directdraw_sink_check_offscreen_surface (ddrawsink);
  if (!ret) {
    GST_ELEMENT_ERROR (ddrawsink, CORE, NEGOTIATION,
        ("Can't create a directdraw offscreen surface with the input caps"),
        (NULL));
  }

  return ret;
}

static GstStateChangeReturn
gst_directdraw_sink_change_state (GstElement * element,
    GstStateChange transition)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (element);
  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
      if (!gst_directdraw_sink_setup_ddraw (ddrawsink)) {
        ret = GST_STATE_CHANGE_FAILURE;
        goto beach;
      }

      if (!(ddrawsink->caps = gst_directdraw_sink_get_ddrawcaps (ddrawsink))) {
        ret = GST_STATE_CHANGE_FAILURE;
        goto beach;
      }
      break;
    default:
      break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      ddrawsink->fps_n = 0;
      ddrawsink->fps_d = 1;
      ddrawsink->video_width = 0;
      ddrawsink->video_height = 0;
      if (ddrawsink->buffer_pool)
        gst_directdraw_sink_bufferpool_clear (ddrawsink);
      break;
    case GST_STATE_CHANGE_READY_TO_NULL:
      if (ddrawsink->setup)
        gst_directdraw_sink_cleanup (ddrawsink);
      break;
    default:
      break;
  }

beach:
  return ret;
}

static GstFlowReturn
gst_directdraw_sink_buffer_alloc (GstBaseSink * bsink, guint64 offset,
    guint size, GstCaps * caps, GstBuffer ** buf)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (bsink);
  GstStructure *structure;
  gint width, height;
  GstDDrawSurface *surface = NULL;
  GstFlowReturn ret = GST_FLOW_OK;
  GstCaps *buffer_caps = caps;
  gboolean buffercaps_unref = FALSE;

  GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
      "a buffer of %u bytes was requested", size);

  structure = gst_caps_get_structure (caps, 0);
  if (!gst_structure_get_int (structure, "width", &width) ||
      !gst_structure_get_int (structure, "height", &height)) {
    GST_WARNING_OBJECT (ddrawsink, "invalid caps for buffer allocation %"
        GST_PTR_FORMAT, caps);
    return GST_FLOW_UNEXPECTED;
  }

  g_mutex_lock (ddrawsink->pool_lock);

  /* Inspect our buffer pool */
  while (ddrawsink->buffer_pool) {
    surface = (GstDDrawSurface *) ddrawsink->buffer_pool->data;
    if (surface) {
      /* Removing from the pool */
      ddrawsink->buffer_pool = g_slist_delete_link (ddrawsink->buffer_pool,
          ddrawsink->buffer_pool);

      /* If the surface is invalid for our need, destroy */
      if ((surface->width != width) ||
          (surface->height != height) ||
          (memcmp (&surface->dd_pixel_format, &ddrawsink->dd_pixel_format,
                  sizeof (DDPIXELFORMAT)) ||
              !gst_directdraw_sink_surface_check (ddrawsink, surface))
          ) {
        gst_directdraw_sink_surface_destroy (ddrawsink, surface);
        gst_buffer_unref (GST_BUFFER_CAST (surface));
        surface = NULL;
      } else {
        /* We found a suitable surface */
        break;
      }
    }
  }

  if (!ddrawsink->can_blit_between_colorspace) {
    /* Hardware doesn't support blit from one colorspace to another.
     * Check if the colorspace of the current display mode has changed since
     * the last negociation. If it's the case, we will have to renegociate
     */
    guint depth;
    HRESULT hres;
    DDSURFACEDESC2 surface_desc;
    DDSURFACEDESC2 *sd;

    if (!gst_structure_get_int (structure, "depth", (gint *) & depth)) {
      GST_CAT_DEBUG_OBJECT (directdrawsink_debug, ddrawsink,
          "Can't get depth from buffer_alloc caps");
      return GST_FLOW_ERROR;
    }
    surface_desc.dwSize = sizeof (surface_desc);
    sd = &surface_desc;
    hres =
        IDirectDraw7_GetDisplayMode (ddrawsink->ddraw_object,
        (DDSURFACEDESC *) sd);
    if (hres != DD_OK) {
      GST_CAT_DEBUG_OBJECT (directdrawsink_debug, ddrawsink,
          "Can't get current display mode (error=%ld)", (glong) hres);
      return GST_FLOW_ERROR;
    }

    if (depth != gst_directdraw_sink_get_depth (&surface_desc.ddpfPixelFormat)) {
      GstCaps *copy_caps = NULL;
      GstStructure *copy_structure = NULL;
      GstCaps *display_caps = NULL;
      GstStructure *display_structure = NULL;

      /* make a copy of the original caps */
      copy_caps = gst_caps_copy (caps);
      copy_structure = gst_caps_get_structure (copy_caps, 0);

      display_caps =
          gst_directdraw_sink_create_caps_from_surfacedesc (&surface_desc);
      if (display_caps) {
        display_structure = gst_caps_get_structure (display_caps, 0);
        if (display_structure) {
          gint bpp, endianness, red_mask, green_mask, blue_mask;

          /* get new display mode properties */
          gst_structure_get_int (display_structure, "depth", (gint *) & depth);
          gst_structure_get_int (display_structure, "bpp", &bpp);
          gst_structure_get_int (display_structure, "endianness", &endianness);
          gst_structure_get_int (display_structure, "red_mask", &red_mask);
          gst_structure_get_int (display_structure, "green_mask", &green_mask);
          gst_structure_get_int (display_structure, "blue_mask", &blue_mask);

          /* apply the new display mode changes to the previous caps */
          gst_structure_set (copy_structure,
              "bpp", G_TYPE_INT, bpp,
              "depth", G_TYPE_INT, depth,
              "endianness", G_TYPE_INT, endianness,
              "red_mask", G_TYPE_INT, red_mask,
              "green_mask", G_TYPE_INT, green_mask,
              "blue_mask", G_TYPE_INT, blue_mask, NULL);

          if (gst_pad_peer_accept_caps (GST_VIDEO_SINK_PAD (ddrawsink),
                  copy_caps)) {
            buffer_caps = copy_caps;
            buffercaps_unref = TRUE;
            /* update buffer size needed to store video frames according to new caps */
            size = width * height * (bpp / 8);

            /* update our member pixel format */
            gst_ddrawvideosink_get_format_from_caps (ddrawsink, buffer_caps,
                &ddrawsink->dd_pixel_format);
            ddrawsink->must_recreate_offscreen = TRUE;

            GST_CAT_DEBUG_OBJECT (directdrawsink_debug, ddrawsink,
                " desired caps %s \n\n new caps %s", gst_caps_to_string (caps),
                gst_caps_to_string (buffer_caps));
          } else {
            GST_CAT_DEBUG_OBJECT (directdrawsink_debug, ddrawsink,
                "peer refused caps re-negociation "
                "and we can't render with the current caps.");
            ret = GST_FLOW_ERROR;
          }
        }
        gst_caps_unref (display_caps);
      }

      if (!buffercaps_unref)
        gst_caps_unref (copy_caps);
    }
  }

  /* We haven't found anything, creating a new one */
  if (!surface) {
    surface = gst_directdraw_sink_surface_create (ddrawsink, buffer_caps, size);
  }

  /* Now we should have a surface, set appropriate caps on it */
  if (surface) {
    GST_BUFFER_FLAGS (GST_BUFFER (surface)) = 0;
    gst_buffer_set_caps (GST_BUFFER (surface), buffer_caps);
  }

  g_mutex_unlock (ddrawsink->pool_lock);

  *buf = GST_BUFFER (surface);

  if (buffercaps_unref)
    gst_caps_unref (buffer_caps);

  return ret;
}

static void
gst_directdraw_sink_draw_borders (GstDirectDrawSink * ddrawsink, RECT dst_rect)
{
  RECT win_rect, fill_rect;
  POINT win_point;
  HDC hdc;

  g_return_if_fail (GST_IS_DIRECTDRAW_SINK (ddrawsink));

  /* Get the target window rect */
  win_point.x = 0;
  win_point.y = 0;
  ClientToScreen (ddrawsink->video_window, &win_point);
  GetClientRect (ddrawsink->video_window, &win_rect);
  OffsetRect (&win_rect, win_point.x, win_point.y);

  /* We acquire a drawing context */
  if ((hdc = GetDC (ddrawsink->video_window))) {
    HBRUSH brush = CreateSolidBrush (RGB (0, 0, 0));

    /* arrange for logical coordinates that match screen coordinates */
    SetWindowOrgEx (hdc, win_point.x, win_point.y, NULL);
    /* Left border */
    if (dst_rect.left > win_rect.left) {
      fill_rect.left = win_rect.left;
      fill_rect.top = win_rect.top;
      fill_rect.bottom = win_rect.bottom;
      fill_rect.right = dst_rect.left;
      FillRect (hdc, &fill_rect, brush);
    }
    /* Right border */
    if (dst_rect.right < win_rect.right) {
      fill_rect.top = win_rect.top;
      fill_rect.left = dst_rect.right;
      fill_rect.bottom = win_rect.bottom;
      fill_rect.right = win_rect.right;
      FillRect (hdc, &fill_rect, brush);
    }
    /* Top border */
    if (dst_rect.top > win_rect.top) {
      fill_rect.top = win_rect.top;
      fill_rect.left = win_rect.left;
      fill_rect.right = win_rect.right;
      fill_rect.bottom = dst_rect.top;
      FillRect (hdc, &fill_rect, brush);
    }
    /* Bottom border */
    if (dst_rect.bottom < win_rect.bottom) {
      fill_rect.top = dst_rect.bottom;
      fill_rect.left = win_rect.left;
      fill_rect.right = win_rect.right;
      fill_rect.bottom = win_rect.bottom;
      FillRect (hdc, &fill_rect, brush);
    }
    DeleteObject (brush);
    ReleaseDC (ddrawsink->video_window, hdc);
  }
}

static GstFlowReturn
gst_directdraw_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
{
  GstDirectDrawSink *ddrawsink = GST_DIRECTDRAW_SINK (bsink);
  HRESULT hRes;
  RECT destsurf_rect, src_rect;
  POINT dest_surf_point;

  if (buf) {
    /* save a reference to the input buffer */
    gst_buffer_ref (buf);
    if (ddrawsink->last_buffer != NULL)
      gst_buffer_unref (ddrawsink->last_buffer);
    ddrawsink->last_buffer = buf;
  } else {
    /* use last buffer */
    buf = ddrawsink->last_buffer;
  }

  if (buf == NULL) {
    GST_ERROR_OBJECT (ddrawsink, "No buffer to render.");
    return GST_FLOW_ERROR;
  } else if (!ddrawsink->video_window) {
    GST_WARNING_OBJECT (ddrawsink, "No video window to render to.");
    return GST_FLOW_ERROR;
  }

  /* get the video window position */
  GST_OBJECT_LOCK (ddrawsink);
  if (G_UNLIKELY (!ddrawsink->video_window)) {
    GST_OBJECT_UNLOCK (ddrawsink);
    GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
        "gst_directdraw_sink_show_frame our video window disappeared");
    GST_ELEMENT_ERROR (ddrawsink, RESOURCE, NOT_FOUND,
        ("Output window was closed"), (NULL));
    return GST_FLOW_ERROR;
  }
  dest_surf_point.x = 0;
  dest_surf_point.y = 0;
  ClientToScreen (ddrawsink->video_window, &dest_surf_point);
  GetClientRect (ddrawsink->video_window, &destsurf_rect);
  OffsetRect (&destsurf_rect, dest_surf_point.x, dest_surf_point.y);

  /* Check to see if we have an area to draw to.
   * When the window is minimized, it will trigger the
   * "IDirectDrawSurface7_Blt (object's offscreen surface)" warning,
   * with a msg that the rectangle is invalid */
  if (destsurf_rect.right <= destsurf_rect.left ||
      destsurf_rect.bottom <= destsurf_rect.top) {
    GST_OBJECT_UNLOCK (ddrawsink);
    GST_DEBUG_OBJECT (ddrawsink, "invalid rendering window rectangle "
        "(%ld, %ld), (%ld, %ld)", destsurf_rect.left, destsurf_rect.top,
        destsurf_rect.right, destsurf_rect.bottom);
    goto beach;
  }

  if (ddrawsink->keep_aspect_ratio) {
    /* center image to dest image keeping aspect ratio */
    src_rect.top = 0;
    src_rect.left = 0;
    src_rect.bottom = ddrawsink->video_height;
    src_rect.right = ddrawsink->video_width;
    gst_directdraw_sink_center_rect (ddrawsink, src_rect, destsurf_rect,
        &destsurf_rect);
    gst_directdraw_sink_draw_borders (ddrawsink, destsurf_rect);
  }
  GST_OBJECT_UNLOCK (ddrawsink);

  if (ddrawsink->must_recreate_offscreen && ddrawsink->offscreen_surface) {
    IDirectDrawSurface7_Release (ddrawsink->offscreen_surface);
    ddrawsink->offscreen_surface = NULL;
  }

  /* check for surfaces lost */
  if (!gst_directdraw_sink_check_primary_surface (ddrawsink) ||
      !gst_directdraw_sink_check_offscreen_surface (ddrawsink)) {
    return GST_FLOW_ERROR;
  }

  if (!GST_IS_DDRAWSURFACE (buf) ||
      ((GST_IS_DDRAWSURFACE (buf)) && (GST_BUFFER (buf)->malloc_data))) {
    /* We are receiving a system memory buffer so we will copy 
       to the memory of our offscreen surface and next blit this surface 
       on the primary surface */
    LPBYTE data = NULL;
    guint src_pitch, line;
    DDSURFACEDESC2 surf_desc;
    DDSURFACEDESC2 *sd;

    ZeroMemory (&surf_desc, sizeof (surf_desc));
    surf_desc.dwSize = sizeof (surf_desc);
    sd = &surf_desc;

    /* Lock the surface */
    hRes =
        IDirectDrawSurface7_Lock (ddrawsink->offscreen_surface, NULL,
        (DDSURFACEDESC *) sd, DDLOCK_WAIT, NULL);
    if (hRes != DD_OK) {
      GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
          "gst_directdraw_sink_show_frame failed locking surface %s",
          DDErrorString (hRes));

      if (IDirectDrawSurface7_IsLost (ddrawsink->offscreen_surface) == DD_OK)
        return GST_FLOW_OK;
      else
        return GST_FLOW_ERROR;
    }

    /* Write each line respecting the destination surface pitch */
    data = surf_desc.lpSurface;
    if (ddrawsink->video_height) {
      src_pitch = GST_BUFFER_SIZE (buf) / ddrawsink->video_height;
      for (line = 0; line < surf_desc.dwHeight; line++) {
        memcpy (data, GST_BUFFER_DATA (buf) + (line * src_pitch), src_pitch);
        data += surf_desc.lPitch;
      }
    }

    /* Unlock the surface */
    hRes = IDirectDrawSurface7_Unlock (ddrawsink->offscreen_surface, NULL);
    if (hRes != DD_OK) {
      GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
          "gst_directdraw_sink_show_frame failed unlocking surface %s",
          DDErrorString (hRes));
      return GST_FLOW_ERROR;
    }

    /* blit to primary surface ( Blt will scale the video the dest rect surface
     * if needed */
    hRes = IDirectDrawSurface7_Blt (ddrawsink->primary_surface, &destsurf_rect,
        ddrawsink->offscreen_surface, NULL, DDBLT_WAIT, NULL);
    if (hRes != DD_OK)          /* FIXME: Is it really safe to continue past here ? */
      GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
          "IDirectDrawSurface7_Blt (object's offscreen surface) " "returned %s",
          DDErrorString (hRes));

  } else {
    /* We are receiving a directdraw surface (previously returned by our buffer
     * pool so we will simply blit it on the primary surface */
    GstDDrawSurface *surface = NULL;

    surface = GST_DDRAWSURFACE (buf);

    /* Unlocking surface before blit */
    IDirectDrawSurface7_Unlock (surface->surface, NULL);
    surface->locked = FALSE;

    /* blit to our primary surface */
    hRes = IDirectDrawSurface7_Blt (ddrawsink->primary_surface, &destsurf_rect,
        surface->surface, NULL, DDBLT_WAIT, NULL);
    if (hRes != DD_OK)          /* FIXME: Is it really safe to continue past here ? */
      GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
          "IDirectDrawSurface7_Blt (offscreen surface from buffer_alloc) "
          "returned %s", DDErrorString (hRes));
  }

beach:
  return GST_FLOW_OK;
}

static void
gst_directdraw_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
    GstClockTime * start, GstClockTime * end)
{
  GstDirectDrawSink *ddrawsink;

  ddrawsink = GST_DIRECTDRAW_SINK (bsink);

  if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
    *start = GST_BUFFER_TIMESTAMP (buf);
    if (GST_BUFFER_DURATION_IS_VALID (buf)) {
      *end = *start + GST_BUFFER_DURATION (buf);
    } else {
      if (ddrawsink->fps_n > 0) {
        *end = *start + (GST_SECOND * ddrawsink->fps_d) / ddrawsink->fps_n;
      }
    }
  }
}

/* Utility functions */

/* this function fill a DDPIXELFORMAT using Gstreamer caps */
static gboolean
gst_ddrawvideosink_get_format_from_caps (GstDirectDrawSink * ddrawsink,
    GstCaps * caps, DDPIXELFORMAT * pPixelFormat)
{
  GstStructure *structure = NULL;
  gboolean ret = TRUE;

  /* check params */
  g_return_val_if_fail (pPixelFormat, FALSE);
  g_return_val_if_fail (caps, FALSE);

  /* init structure */
  memset (pPixelFormat, 0, sizeof (DDPIXELFORMAT));
  pPixelFormat->dwSize = sizeof (DDPIXELFORMAT);

  if (!(structure = gst_caps_get_structure (caps, 0))) {
    GST_CAT_ERROR_OBJECT (directdrawsink_debug, ddrawsink,
        "can't get structure pointer from caps");
    return FALSE;
  }

  if (gst_structure_has_name (structure, "video/x-raw-rgb")) {
    gint depth, bitcount, bitmask, endianness;

    pPixelFormat->dwFlags = DDPF_RGB;
    ret &= gst_structure_get_int (structure, "bpp", &bitcount);
    pPixelFormat->dwRGBBitCount = bitcount;
    ret &= gst_structure_get_int (structure, "depth", &depth);
    ret &= gst_structure_get_int (structure, "red_mask", &bitmask);
    pPixelFormat->dwRBitMask = bitmask;
    ret &= gst_structure_get_int (structure, "green_mask", &bitmask);
    pPixelFormat->dwGBitMask = bitmask;
    ret &= gst_structure_get_int (structure, "blue_mask", &bitmask);
    pPixelFormat->dwBBitMask = bitmask;

    gst_structure_get_int (structure, "endianness", &endianness);
    if (endianness == G_BIG_ENDIAN) {
      endianness = G_LITTLE_ENDIAN;
      pPixelFormat->dwRBitMask = GUINT32_TO_BE (pPixelFormat->dwRBitMask);
      pPixelFormat->dwGBitMask = GUINT32_TO_BE (pPixelFormat->dwGBitMask);
      pPixelFormat->dwBBitMask = GUINT32_TO_BE (pPixelFormat->dwBBitMask);
    }
  } else if (gst_structure_has_name (structure, "video/x-raw-yuv")) {
    guint32 fourcc;

    pPixelFormat->dwFlags = DDPF_FOURCC;
    ret &= gst_structure_get_fourcc (structure, "format", &fourcc);
    pPixelFormat->dwFourCC = fourcc;
  } else {
    GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
        "unknown caps name received %" GST_PTR_FORMAT, caps);
    ret = FALSE;
  }

  return ret;
}

/* This function centers the RECT of source surface to
a dest surface and set the result RECT into result */
static void
gst_directdraw_sink_center_rect (GstDirectDrawSink * ddrawsink, RECT src,
    RECT dst, RECT * result)
{
  gdouble src_ratio, dst_ratio;
  long src_width = src.right;
  long src_height = src.bottom;
  long dst_width = dst.right - dst.left;
  long dst_heigth = dst.bottom - dst.top;
  long result_width = 0, result_height = 0;

  g_return_if_fail (result != NULL);

  src_ratio = (gdouble) src_width / src_height;
  dst_ratio = (gdouble) dst_width / dst_heigth;

  if (src_ratio > dst_ratio) {
    /* new height */
    result_height = (long) (dst_width / src_ratio);

    result->left = dst.left;
    result->right = dst.right;
    result->top = dst.top + (dst_heigth - result_height) / 2;
    result->bottom = result->top + result_height;

  } else if (src_ratio < dst_ratio) {
    /* new width */
    result_width = (long) (dst_heigth * src_ratio);

    result->top = dst.top;
    result->bottom = dst.bottom;
    result->left = dst.left + (dst_width - result_width) / 2;
    result->right = result->left + result_width;

  } else {
    /* same ratio */
    memcpy (result, &dst, sizeof (RECT));
  }

  GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
      "source is %ldx%ld dest is %ldx%ld, result is %ldx%ld with x,y %ldx%ld",
      src_width, src_height, dst_width, dst_heigth,
      result->right - result->left, result->bottom - result->top, result->left,
      result->right);
}

/**
 * Get DirectDraw error message.
 * @hr: HRESULT code
 * Returns: Text representation of the error.
 */
static const char *
DDErrorString (HRESULT hr)
{
  switch (hr) {
    case DDERR_ALREADYINITIALIZED:
      return "DDERR_ALREADYINITIALIZED";
    case DDERR_CANNOTATTACHSURFACE:
      return "DDERR_CANNOTATTACHSURFACE";
    case DDERR_CANNOTDETACHSURFACE:
      return "DDERR_CANNOTDETACHSURFACE";
    case DDERR_CURRENTLYNOTAVAIL:
      return "DDERR_CURRENTLYNOTAVAIL";
    case DDERR_EXCEPTION:
      return "DDERR_EXCEPTION";
    case DDERR_GENERIC:
      return "DDERR_GENERIC";
    case DDERR_HEIGHTALIGN:
      return "DDERR_HEIGHTALIGN";
    case DDERR_INCOMPATIBLEPRIMARY:
      return "DDERR_INCOMPATIBLEPRIMARY";
    case DDERR_INVALIDCAPS:
      return "DDERR_INVALIDCAPS";
    case DDERR_INVALIDCLIPLIST:
      return "DDERR_INVALIDCLIPLIST";
    case DDERR_INVALIDMODE:
      return "DDERR_INVALIDMODE";
    case DDERR_INVALIDOBJECT:
      return "DDERR_INVALIDOBJECT";
    case DDERR_INVALIDPARAMS:
      return "DDERR_INVALIDPARAMS";
    case DDERR_INVALIDPIXELFORMAT:
      return "DDERR_INVALIDPIXELFORMAT";
    case DDERR_INVALIDRECT:
      return "DDERR_INVALIDRECT";
    case DDERR_LOCKEDSURFACES:
      return "DDERR_LOCKEDSURFACES";
    case DDERR_NO3D:
      return "DDERR_NO3D";
    case DDERR_NOALPHAHW:
      return "DDERR_NOALPHAHW";
    case DDERR_NOCLIPLIST:
      return "DDERR_NOCLIPLIST";
    case DDERR_NOCOLORCONVHW:
      return "DDERR_NOCOLORCONVHW";
    case DDERR_NOCOOPERATIVELEVELSET:
      return "DDERR_NOCOOPERATIVELEVELSET";
    case DDERR_NOCOLORKEY:
      return "DDERR_NOCOLORKEY";
    case DDERR_NOCOLORKEYHW:
      return "DDERR_NOCOLORKEYHW";
    case DDERR_NODIRECTDRAWSUPPORT:
      return "DDERR_NODIRECTDRAWSUPPORT";
    case DDERR_NOEXCLUSIVEMODE:
      return "DDERR_NOEXCLUSIVEMODE";
    case DDERR_NOFLIPHW:
      return "DDERR_NOFLIPHW";
    case DDERR_NOGDI:
      return "DDERR_NOGDI";
    case DDERR_NOMIRRORHW:
      return "DDERR_NOMIRRORHW";
    case DDERR_NOTFOUND:
      return "DDERR_NOTFOUND";
    case DDERR_NOOVERLAYHW:
      return "DDERR_NOOVERLAYHW";
    case DDERR_NORASTEROPHW:
      return "DDERR_NORASTEROPHW";
    case DDERR_NOROTATIONHW:
      return "DDERR_NOROTATIONHW";
    case DDERR_NOSTRETCHHW:
      return "DDERR_NOSTRETCHHW";
    case DDERR_NOT4BITCOLOR:
      return "DDERR_NOT4BITCOLOR";
    case DDERR_NOT4BITCOLORINDEX:
      return "DDERR_NOT4BITCOLORINDEX";
    case DDERR_NOT8BITCOLOR:
      return "DDERR_NOT8BITCOLOR";
    case DDERR_NOTEXTUREHW:
      return "DDERR_NOTEXTUREHW";
    case DDERR_NOVSYNCHW:
      return "DDERR_NOVSYNCHW";
    case DDERR_NOZBUFFERHW:
      return "DDERR_NOZBUFFERHW";
    case DDERR_NOZOVERLAYHW:
      return "DDERR_NOZOVERLAYHW";
    case DDERR_OUTOFCAPS:
      return "DDERR_OUTOFCAPS";
    case DDERR_OUTOFMEMORY:
      return "DDERR_OUTOFMEMORY";
    case DDERR_OUTOFVIDEOMEMORY:
      return "DDERR_OUTOFVIDEOMEMORY";
    case DDERR_OVERLAYCANTCLIP:
      return "DDERR_OVERLAYCANTCLIP";
    case DDERR_OVERLAYCOLORKEYONLYONEACTIVE:
      return "DDERR_OVERLAYCOLORKEYONLYONEACTIVE";
    case DDERR_PALETTEBUSY:
      return "DDERR_PALETTEBUSY";
    case DDERR_COLORKEYNOTSET:
      return "DDERR_COLORKEYNOTSET";
    case DDERR_SURFACEALREADYATTACHED:
      return "DDERR_SURFACEALREADYATTACHED";
    case DDERR_SURFACEALREADYDEPENDENT:
      return "DDERR_SURFACEALREADYDEPENDENT";
    case DDERR_SURFACEBUSY:
      return "DDERR_SURFACEBUSY";
    case DDERR_CANTLOCKSURFACE:
      return "DDERR_CANTLOCKSURFACE";
    case DDERR_SURFACEISOBSCURED:
      return "DDERR_SURFACEISOBSCURED";
    case DDERR_SURFACELOST:
      return "DDERR_SURFACELOST";
    case DDERR_SURFACENOTATTACHED:
      return "DDERR_SURFACENOTATTACHED";
    case DDERR_TOOBIGHEIGHT:
      return "DDERR_TOOBIGHEIGHT";
    case DDERR_TOOBIGSIZE:
      return "DDERR_TOOBIGSIZE";
    case DDERR_TOOBIGWIDTH:
      return "DDERR_TOOBIGWIDTH";
    case DDERR_UNSUPPORTED:
      return "DDERR_UNSUPPORTED";
    case DDERR_UNSUPPORTEDFORMAT:
      return "DDERR_UNSUPPORTEDFORMAT";
    case DDERR_UNSUPPORTEDMASK:
      return "DDERR_UNSUPPORTEDMASK";
    case DDERR_VERTICALBLANKINPROGRESS:
      return "DDERR_VERTICALBLANKINPROGRESS";
    case DDERR_WASSTILLDRAWING:
      return "DDERR_WASSTILLDRAWING";
    case DDERR_XALIGN:
      return "DDERR_XALIGN";
    case DDERR_INVALIDDIRECTDRAWGUID:
      return "DDERR_INVALIDDIRECTDRAWGUID";
    case DDERR_DIRECTDRAWALREADYCREATED:
      return "DDERR_DIRECTDRAWALREADYCREATED";
    case DDERR_NODIRECTDRAWHW:
      return "DDERR_NODIRECTDRAWHW";
    case DDERR_PRIMARYSURFACEALREADYEXISTS:
      return "DDERR_PRIMARYSURFACEALREADYEXISTS";
    case DDERR_NOEMULATION:
      return "DDERR_NOEMULATION";
    case DDERR_REGIONTOOSMALL:
      return "DDERR_REGIONTOOSMALL";
    case DDERR_CLIPPERISUSINGHWND:
      return "DDERR_CLIPPERISUSINGHWND";
    case DDERR_NOCLIPPERATTACHED:
      return "DDERR_NOCLIPPERATTACHED";
    case DDERR_NOHWND:
      return "DDERR_NOHWND";
    case DDERR_HWNDSUBCLASSED:
      return "DDERR_HWNDSUBCLASSED";
    case DDERR_HWNDALREADYSET:
      return "DDERR_HWNDALREADYSET";
    case DDERR_NOPALETTEATTACHED:
      return "DDERR_NOPALETTEATTACHED";
    case DDERR_NOPALETTEHW:
      return "DDERR_NOPALETTEHW";
    case DDERR_BLTFASTCANTCLIP:
      return "DDERR_BLTFASTCANTCLIP";
    case DDERR_NOBLTHW:
      return "DDERR_NOBLTHW";
    case DDERR_NODDROPSHW:
      return "DDERR_NODDROPSHW";
    case DDERR_OVERLAYNOTVISIBLE:
      return "DDERR_OVERLAYNOTVISIBLE";
    case DDERR_NOOVERLAYDEST:
      return "DDERR_NOOVERLAYDEST";
    case DDERR_INVALIDPOSITION:
      return "DDERR_INVALIDPOSITION";
    case DDERR_NOTAOVERLAYSURFACE:
      return "DDERR_NOTAOVERLAYSURFACE";
    case DDERR_EXCLUSIVEMODEALREADYSET:
      return "DDERR_EXCLUSIVEMODEALREADYSET";
    case DDERR_NOTFLIPPABLE:
      return "DDERR_NOTFLIPPABLE";
    case DDERR_CANTDUPLICATE:
      return "DDERR_CANTDUPLICATE";
    case DDERR_NOTLOCKED:
      return "DDERR_NOTLOCKED";
    case DDERR_CANTCREATEDC:
      return "DDERR_CANTCREATEDC";
    case DDERR_NODC:
      return "DDERR_NODC";
    case DDERR_WRONGMODE:
      return "DDERR_WRONGMODE";
    case DDERR_IMPLICITLYCREATED:
      return "DDERR_IMPLICITLYCREATED";
    case DDERR_NOTPALETTIZED:
      return "DDERR_NOTPALETTIZED";
    case DDERR_UNSUPPORTEDMODE:
      return "DDERR_UNSUPPORTEDMODE";
    case DDERR_NOMIPMAPHW:
      return "DDERR_NOMIPMAPHW";
    case DDERR_INVALIDSURFACETYPE:
      return "DDERR_INVALIDSURFACETYPE";
    case DDERR_DCALREADYCREATED:
      return "DDERR_DCALREADYCREATED";
    case DDERR_CANTPAGELOCK:
      return "DDERR_CANTPAGELOCK";
    case DDERR_CANTPAGEUNLOCK:
      return "DDERR_CANTPAGEUNLOCK";
    case DDERR_NOTPAGELOCKED:
      return "DDERR_NOTPAGELOCKED";
    case DDERR_NOTINITIALIZED:
      return "DDERR_NOTINITIALIZED";
  }
  return "Unknown Error";
}

static gboolean
gst_directdraw_sink_setup_ddraw (GstDirectDrawSink * ddrawsink)
{
  gboolean bRet = TRUE;
  HRESULT hRes;
  /* create an instance of the ddraw object use DDCREATE_EMULATIONONLY as first
   * parameter to force Directdraw to use the hardware emulation layer */
  hRes = DirectDrawCreateEx ( /*DDCREATE_EMULATIONONLY */ 0,
      (void **) &ddrawsink->ddraw_object, &IID_IDirectDraw7, NULL);
  if (hRes != DD_OK || ddrawsink->ddraw_object == NULL) {
    GST_ELEMENT_ERROR (ddrawsink, RESOURCE, WRITE,
        ("Failed to create the DirectDraw object error=%s",
            DDErrorString (hRes)), (NULL));
    return FALSE;
  }

  /* set cooperative level */
  hRes = IDirectDraw7_SetCooperativeLevel (ddrawsink->ddraw_object,
      NULL, DDSCL_NORMAL);
  if (hRes != DD_OK) {
    GST_ELEMENT_ERROR (ddrawsink, RESOURCE, WRITE,
        ("Failed to set the set the cooperative level error=%s",
            DDErrorString (hRes)), (NULL));
    return FALSE;
  }

  /* setup the clipper object */
  hRes = IDirectDraw7_CreateClipper (ddrawsink->ddraw_object, 0,
      &ddrawsink->clipper, NULL);

  if (hRes == DD_OK && ddrawsink->video_window)
    IDirectDrawClipper_SetHWnd (ddrawsink->clipper, 0, ddrawsink->video_window);

  /* create our primary surface */
  if (!gst_directdraw_sink_check_primary_surface (ddrawsink))
    return FALSE;

  /* directdraw objects are setup */
  ddrawsink->setup = TRUE;

  return bRet;
}

static long FAR PASCAL
WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message) {
    case WM_CREATE:{
      LPCREATESTRUCT crs = (LPCREATESTRUCT) lParam;
      /* Nail pointer to the video sink down to this window */
      SetWindowLongPtr (hWnd, GWLP_USERDATA, (LONG_PTR) crs->lpCreateParams);
      break;
    }
    case WM_SIZE:
    case WM_CHAR:
    case WM_KEYDOWN:
    case WM_KEYUP:
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_MBUTTONUP:
    case WM_MOUSEMOVE:{
      GstDirectDrawSink *ddrawsink;
      ddrawsink = (GstDirectDrawSink *) GetWindowLongPtr (hWnd, GWLP_USERDATA);

      if (G_UNLIKELY (!ddrawsink))
        break;

      switch (message) {
        case WM_SIZE:{
          GST_OBJECT_LOCK (ddrawsink);
          ddrawsink->out_width = LOWORD (lParam);
          ddrawsink->out_height = HIWORD (lParam);
          GST_OBJECT_UNLOCK (ddrawsink);
          GST_DEBUG_OBJECT (ddrawsink, "Window size is %dx%d", LOWORD (wParam),
              HIWORD (wParam));
          break;
        }
        case WM_CHAR:
        case WM_KEYDOWN:
        case WM_KEYUP:{
          gunichar2 wcrep[128];
          if (GetKeyNameTextW (lParam, wcrep, 128)) {
            gchar *utfrep = g_utf16_to_utf8 (wcrep, 128, NULL, NULL, NULL);
            if (utfrep) {
              if (message == WM_CHAR || message == WM_KEYDOWN)
                gst_navigation_send_key_event (GST_NAVIGATION (ddrawsink),
                    "key-press", utfrep);
              if (message == WM_CHAR || message == WM_KEYUP)
                gst_navigation_send_key_event (GST_NAVIGATION (ddrawsink),
                    "key-release", utfrep);
              g_free (utfrep);
            }
          }
          break;
        }
        case WM_LBUTTONDOWN:
        case WM_LBUTTONUP:
        case WM_RBUTTONDOWN:
        case WM_RBUTTONUP:
        case WM_MBUTTONDOWN:
        case WM_MBUTTONUP:
        case WM_MOUSEMOVE:{
          gint x, y, button;
          const gchar *action;

          switch (message) {
            case WM_MOUSEMOVE:
              button = 0;
              action = "mouse-move";
              break;
            case WM_LBUTTONDOWN:
              button = 1;
              action = "mouse-button-press";
              break;
            case WM_LBUTTONUP:
              button = 1;
              action = "mouse-button-release";
              break;
            case WM_RBUTTONDOWN:
              button = 2;
              action = "mouse-button-press";
              break;
            case WM_RBUTTONUP:
              button = 2;
              action = "mouse-button-release";
              break;
            case WM_MBUTTONDOWN:
              button = 3;
              action = "mouse-button-press";
              break;
            case WM_MBUTTONUP:
              button = 3;
              action = "mouse-button-release";
              break;
            default:
              button = 4;
              action = NULL;
          }

          x = LOWORD (lParam);
          y = HIWORD (lParam);

          if (button == 0) {
            GST_DEBUG_OBJECT (ddrawsink, "Mouse moved to %dx%d", x, y);
          } else
            GST_DEBUG_OBJECT (ddrawsink, "Mouse button %d pressed at %dx%d",
                button, x, y);

          if (button < 4)
            gst_navigation_send_mouse_event (GST_NAVIGATION (ddrawsink),
                action, button, x, y);

          break;
        }
      }
      break;
    }
    case WM_ERASEBKGND:
      return TRUE;
    case WM_CLOSE:
      DestroyWindow (hWnd);
    case WM_DESTROY:
      PostQuitMessage (0);
      return 0;
  }

  return DefWindowProc (hWnd, message, wParam, lParam);
}

static gpointer
gst_directdraw_sink_window_thread (GstDirectDrawSink * ddrawsink)
{
  WNDCLASS WndClass;
  MSG msg;

  memset (&WndClass, 0, sizeof (WNDCLASS));
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  WndClass.hInstance = GetModuleHandle (NULL);
  WndClass.lpszClassName = "GStreamer-DirectDraw";
  WndClass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.lpfnWndProc = WndProc;
  WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
  RegisterClass (&WndClass);

  ddrawsink->video_window = CreateWindowEx (0, "GStreamer-DirectDraw",
      "GStreamer-DirectDraw sink default window",
      WS_OVERLAPPEDWINDOW | WS_SIZEBOX, 0, 0, 640, 480, NULL, NULL,
      WndClass.hInstance, (LPVOID) ddrawsink);
  if (ddrawsink->video_window == NULL)
    return NULL;

  /* Set the clipper on that window */
  IDirectDrawClipper_SetHWnd (ddrawsink->clipper, 0, ddrawsink->video_window);

  /* signal application we created a window */
  gst_x_overlay_got_window_handle (GST_X_OVERLAY (ddrawsink),
      (guintptr) ddrawsink->video_window);

  ReleaseSemaphore (ddrawsink->window_created_signal, 1, NULL);

  /* start message loop processing our default window messages */
  while (GetMessage (&msg, NULL, 0, 0) != FALSE) {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
  }

  GST_CAT_LOG_OBJECT (directdrawsink_debug, ddrawsink,
      "our window received WM_QUIT or error.");
  /* The window could have changed, if it is not ours anymore we don't
   * overwrite the current video window with NULL */
  if (ddrawsink->our_video_window) {
    GST_OBJECT_LOCK (ddrawsink);
    ddrawsink->video_window = NULL;
    GST_OBJECT_UNLOCK (ddrawsink);
  }

  return NULL;
}

static gboolean
gst_directdraw_sink_create_default_window (GstDirectDrawSink * ddrawsink)
{
  ddrawsink->window_created_signal = CreateSemaphore (NULL, 0, 1, NULL);
  if (ddrawsink->window_created_signal == NULL)
    return FALSE;

  ddrawsink->window_thread = g_thread_create (
      (GThreadFunc) gst_directdraw_sink_window_thread, ddrawsink, TRUE, NULL);

  if (ddrawsink->window_thread == NULL)
    goto failed;

  /* wait maximum 10 seconds for windows creating */
  if (WaitForSingleObject (ddrawsink->window_created_signal,
          10000) != WAIT_OBJECT_0)
    goto failed;

  CloseHandle (ddrawsink->window_created_signal);
  return TRUE;

failed:
  CloseHandle (ddrawsink->window_created_signal);
  GST_ELEMENT_ERROR (ddrawsink, RESOURCE, WRITE,
      ("Error creating our default window"), (NULL));

  return FALSE;
}

static gboolean
gst_directdraw_sink_check_primary_surface (GstDirectDrawSink * ddrawsink)
{
  HRESULT hres;
  DDSURFACEDESC2 dd_surface_desc;
  DDSURFACEDESC2 *sd;

  /* if our primary surface already exist, check if it's not lost */
  if (ddrawsink->primary_surface) {
    if (IDirectDrawSurface7_IsLost (ddrawsink->primary_surface) == DD_OK) {
      /* no problem with our primary surface */
      return TRUE;
    } else {
      /* our primary surface was lost, try to restore it */
      if (IDirectDrawSurface7_Restore (ddrawsink->primary_surface) == DD_OK) {
        /* restore is done */
        GST_CAT_LOG_OBJECT (directdrawsink_debug, ddrawsink,
            "Our primary surface" " was restored after lost");
        return TRUE;
      } else {
        /* failed to restore our primary surface, 
         * probably because the display mode was changed. 
         * Release this surface and recreate a new one.
         */
        GST_CAT_LOG_OBJECT (directdrawsink_debug, ddrawsink,
            "Our primary surface"
            " was lost and display mode has changed. Destroy and recreate our surface.");
        IDirectDrawSurface7_Release (ddrawsink->primary_surface);
        ddrawsink->primary_surface = NULL;

        /* also release offscreen surface */
        IDirectDrawSurface7_Release (ddrawsink->offscreen_surface);
        ddrawsink->offscreen_surface = NULL;
      }
    }
  }

  /* create our primary surface */
  memset (&dd_surface_desc, 0, sizeof (dd_surface_desc));
  dd_surface_desc.dwSize = sizeof (dd_surface_desc);
  dd_surface_desc.dwFlags = DDSD_CAPS;
  dd_surface_desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
  sd = &dd_surface_desc;
  hres =
      IDirectDraw7_CreateSurface (ddrawsink->ddraw_object, (DDSURFACEDESC *) sd,
      &ddrawsink->primary_surface, NULL);
  if (hres != DD_OK) {
    GST_ELEMENT_ERROR (ddrawsink, RESOURCE, WRITE,
        ("Failed to create our primary surface error=%s", DDErrorString (hres)),
        (NULL));
    return FALSE;
  }

  /* attach our clipper object to the new primary surface */
  if (ddrawsink->clipper) {
    hres = IDirectDrawSurface7_SetClipper (ddrawsink->primary_surface,
        ddrawsink->clipper);
  }

  return TRUE;
}

static gboolean
gst_directdraw_sink_check_offscreen_surface (GstDirectDrawSink * ddrawsink)
{
  DDSURFACEDESC2 dd_surface_desc;
  DDSURFACEDESC2 *sd;
  HRESULT hres;

  /* if our offscreen surface already exist, check if it's not lost */
  if (ddrawsink->offscreen_surface) {
    if (IDirectDrawSurface7_IsLost (ddrawsink->offscreen_surface) == DD_OK) {
      /* no problem with our offscreen surface */
      return TRUE;
    } else {
      /* our offscreen surface was lost, try to restore it */
      if (IDirectDrawSurface7_Restore (ddrawsink->offscreen_surface) == DD_OK) {
        /* restore is done */
        GST_CAT_LOG_OBJECT (directdrawsink_debug, ddrawsink,
            "Our offscreen surface" " was restored after lost");
        return TRUE;
      } else {
        /* failed to restore our offscreen surface, 
         * probably because the display mode was changed. 
         * Release this surface and recreate a new one.
         */
        GST_CAT_LOG_OBJECT (directdrawsink_debug, ddrawsink,
            "Our offscreen surface"
            " was lost and display mode has changed. Destroy and recreate our surface.");
        IDirectDrawSurface7_Release (ddrawsink->offscreen_surface);
        ddrawsink->offscreen_surface = NULL;
      }
    }
  }

  memset (&dd_surface_desc, 0, sizeof (dd_surface_desc));
  dd_surface_desc.dwSize = sizeof (dd_surface_desc);
  dd_surface_desc.dwFlags =
      DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
  dd_surface_desc.dwHeight = ddrawsink->video_height;
  dd_surface_desc.dwWidth = ddrawsink->video_width;
  memcpy (&(dd_surface_desc.ddpfPixelFormat), &ddrawsink->dd_pixel_format,
      sizeof (DDPIXELFORMAT));

  dd_surface_desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
  sd = &dd_surface_desc;
  hres =
      IDirectDraw7_CreateSurface (ddrawsink->ddraw_object, (DDSURFACEDESC *) sd,
      &ddrawsink->offscreen_surface, NULL);
  if (hres != DD_OK) {
    GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
        "create_ddraw_surface:CreateSurface (offscreen surface for buffer_pool) failed %s",
        DDErrorString (hres));
    return FALSE;
  }

  ddrawsink->must_recreate_offscreen = FALSE;

  return TRUE;
}

static int
gst_directdraw_sink_get_depth (LPDDPIXELFORMAT lpddpfPixelFormat)
{
  gint order = 0, binary;

  binary =
      lpddpfPixelFormat->
      dwRBitMask | lpddpfPixelFormat->dwGBitMask | lpddpfPixelFormat->
      dwBBitMask | lpddpfPixelFormat->dwRGBAlphaBitMask;
  while (binary != 0) {
    if ((binary % 2) == 1)
      order++;
    binary = binary >> 1;
  }
  return order;
}

static HRESULT WINAPI
EnumModesCallback2 (LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext)
{
  GstDirectDrawSink *ddrawsink = (GstDirectDrawSink *) lpContext;
  GstCaps *format_caps = NULL;
  LPDDSURFACEDESC2 sd;

  if (!ddrawsink || !lpDDSurfaceDesc)
    return DDENUMRET_CANCEL;

  sd = (LPDDSURFACEDESC2) lpDDSurfaceDesc;
  if ((sd->dwFlags & DDSD_PIXELFORMAT) != DDSD_PIXELFORMAT) {
    GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
        "Display mode found with DDSD_PIXELFORMAT not set");
    return DDENUMRET_OK;
  }

  if ((sd->ddpfPixelFormat.dwFlags & DDPF_RGB) != DDPF_RGB)
    return DDENUMRET_OK;

  format_caps = gst_directdraw_sink_create_caps_from_surfacedesc (sd);

  if (format_caps) {
    gst_caps_append (ddrawsink->caps, format_caps);
  }

  return DDENUMRET_OK;
}

static GstCaps *
gst_directdraw_sink_create_caps_from_surfacedesc (LPDDSURFACEDESC2 desc)
{
  GstCaps *caps = NULL;
  gint endianness = G_LITTLE_ENDIAN;
  gint depth;

  if ((desc->ddpfPixelFormat.dwFlags & DDPF_RGB) != DDPF_RGB)
    return NULL;

  depth = gst_directdraw_sink_get_depth (&desc->ddpfPixelFormat);

  if (desc->ddpfPixelFormat.dwRGBBitCount == 24 ||
      desc->ddpfPixelFormat.dwRGBBitCount == 32) {
    /* ffmpegcolorspace handles 24/32 bpp RGB as big-endian. */
    endianness = G_BIG_ENDIAN;
    desc->ddpfPixelFormat.dwRBitMask =
        GUINT32_TO_BE (desc->ddpfPixelFormat.dwRBitMask);
    desc->ddpfPixelFormat.dwGBitMask =
        GUINT32_TO_BE (desc->ddpfPixelFormat.dwGBitMask);
    desc->ddpfPixelFormat.dwBBitMask =
        GUINT32_TO_BE (desc->ddpfPixelFormat.dwBBitMask);
    if (desc->ddpfPixelFormat.dwRGBBitCount == 24) {
      desc->ddpfPixelFormat.dwRBitMask >>= 8;
      desc->ddpfPixelFormat.dwGBitMask >>= 8;
      desc->ddpfPixelFormat.dwBBitMask >>= 8;
    }
  }

  caps = gst_caps_new_simple ("video/x-raw-rgb",
      "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
      "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
      "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1,
      "bpp", G_TYPE_INT, desc->ddpfPixelFormat.dwRGBBitCount,
      "depth", G_TYPE_INT, depth,
      "endianness", G_TYPE_INT, endianness,
      "red_mask", G_TYPE_INT, desc->ddpfPixelFormat.dwRBitMask,
      "green_mask", G_TYPE_INT, desc->ddpfPixelFormat.dwGBitMask,
      "blue_mask", G_TYPE_INT, desc->ddpfPixelFormat.dwBBitMask, NULL);

  return caps;
}

static GstCaps *
gst_directdraw_sink_get_ddrawcaps (GstDirectDrawSink * ddrawsink)
{
  HRESULT hRes = S_OK;
  DDCAPS ddcaps_hardware;
  DDCAPS ddcaps_emulation;
  GstCaps *format_caps = NULL;

  ddrawsink->caps = gst_caps_new_empty ();
  if (!ddrawsink->caps)
    return FALSE;

  /* get hardware caps */
  ddcaps_hardware.dwSize = sizeof (DDCAPS);
  ddcaps_emulation.dwSize = sizeof (DDCAPS);
  IDirectDraw7_GetCaps (ddrawsink->ddraw_object, &ddcaps_hardware,
      &ddcaps_emulation);

  /* we don't test for DDCAPS_BLTSTRETCH on the hardware as the directdraw 
   * emulation layer can do it */
  if (!(ddcaps_hardware.dwCaps & DDCAPS_BLTFOURCC)) {
    DDSURFACEDESC2 surface_desc;
    DDSURFACEDESC2 *sd;

    GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
        "hardware doesn't support blit from one colorspace to another one. "
        "so we will create a caps with only the current display mode");

    /* save blit caps */
    ddrawsink->can_blit_between_colorspace = FALSE;

    surface_desc.dwSize = sizeof (surface_desc);
    sd = &surface_desc;
    hRes =
        IDirectDraw7_GetDisplayMode (ddrawsink->ddraw_object,
        (DDSURFACEDESC *) sd);
    if (hRes != DD_OK) {
      GST_ELEMENT_ERROR (ddrawsink, CORE, NEGOTIATION,
          ("Error getting the current display mode error=%s",
              DDErrorString (hRes)), (NULL));
      return NULL;
    }

    format_caps =
        gst_directdraw_sink_create_caps_from_surfacedesc (&surface_desc);
    if (format_caps) {
      gst_caps_append (ddrawsink->caps, format_caps);
    }

    GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink, "returning caps %s",
        gst_caps_to_string (ddrawsink->caps));
    return ddrawsink->caps;
  }

  GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
      "the hardware can blit from one colorspace to another, "
      "then enumerate the colorspace supported by the hardware");

  /* save blit caps */
  ddrawsink->can_blit_between_colorspace = TRUE;

  /* enumerate display modes exposed by directdraw object 
     to know supported RGB modes */
  hRes =
      IDirectDraw7_EnumDisplayModes (ddrawsink->ddraw_object,
      DDEDM_REFRESHRATES, NULL, ddrawsink, EnumModesCallback2);
  if (hRes != DD_OK) {
    GST_ELEMENT_ERROR (ddrawsink, CORE, NEGOTIATION,
        ("Error enumerating display modes error=%s", DDErrorString (hRes)),
        (NULL));

    return NULL;
  }

  if (gst_caps_is_empty (ddrawsink->caps)) {
    gst_caps_unref (ddrawsink->caps);
    ddrawsink->caps = NULL;
    GST_ELEMENT_ERROR (ddrawsink, CORE, NEGOTIATION,
        ("No supported caps found."), (NULL));
    return NULL;
  }

  /*GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink, "returning caps %s",
   * gst_caps_to_string (ddrawsink->caps)); */

  return ddrawsink->caps;
}

/* Creates miniobject and our internal surface */
static GstDDrawSurface *
gst_directdraw_sink_surface_create (GstDirectDrawSink * ddrawsink,
    GstCaps * caps, size_t size)
{
  GstDDrawSurface *surface = NULL;
  GstStructure *structure = NULL;
  gint pitch;

#if 0
  HRESULT hRes;
#endif
  DDSURFACEDESC2 surf_desc, surf_lock_desc;

  g_return_val_if_fail (GST_IS_DIRECTDRAW_SINK (ddrawsink), NULL);

  /*init structures */
  memset (&surf_desc, 0, sizeof (surf_desc));
  memset (&surf_lock_desc, 0, sizeof (surf_desc));
  surf_desc.dwSize = sizeof (surf_desc);
  surf_lock_desc.dwSize = sizeof (surf_lock_desc);

  /*create miniobject and initialize it */
  surface = (GstDDrawSurface *) gst_mini_object_new (GST_TYPE_DDRAWSURFACE);
  surface->locked = FALSE;

  structure = gst_caps_get_structure (caps, 0);
  if (!gst_structure_get_int (structure, "width", &surface->width) ||
      !gst_structure_get_int (structure, "height", &surface->height)) {
    GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
        "failed getting geometry from caps %" GST_PTR_FORMAT, caps);
  }

  pitch = GST_ROUND_UP_8 (size / surface->height);
  if (!gst_ddrawvideosink_get_format_from_caps (ddrawsink, caps,
          &surface->dd_pixel_format)) {
    GST_CAT_WARNING_OBJECT (directdrawsink_debug, ddrawsink,
        "failed getting pixel format from caps %" GST_PTR_FORMAT, caps);
  }

  /* disable return of directdraw surface to buffer alloc because actually I
   * have no solution to handle display mode changes. The problem is that when
   * the display mode is changed surface's memory is freed then the upstream
   * filter would crash trying to write to this memory. Directdraw has a system
   * lock (DDLOCK_NOSYSLOCK to disable it) to prevent display mode changes 
   * when a surface memory is locked but we need to disable this lock to return
   * multiple buffers (surfaces) and do not lock directdraw API calls.
   */
#if 0
/*  if (ddrawsink->ddraw_object) {*/
  /* Creating an internal surface which will be used as GstBuffer, we used
     the detected pixel format and video dimensions */

  surf_desc.ddsCaps.dwCaps =
      DDSCAPS_OFFSCREENPLAIN /* | DDSCAPS_SYSTEMMEMORY */ ;
  surf_desc.dwFlags =
      DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_PITCH;
  surf_desc.dwHeight = surface->height;
  surf_desc.dwWidth = surface->width;
  memcpy (&(surf_desc.ddpfPixelFormat), &surface->dd_pixel_format,
      sizeof (DDPIXELFORMAT));

  hRes = IDirectDraw7_CreateSurface (ddrawsink->ddraw_object, &surf_desc,
      &surface->surface, NULL);
  if (hRes != DD_OK) {
    goto surface_pitch_bad;
  }

  /* Locking the surface to acquire the memory pointer.
     Use DDLOCK_NOSYSLOCK to disable syslock which can cause a deadlock 
     if directdraw api is used while a buffer is lock */
lock:
  hRes = IDirectDrawSurface7_Lock (surface->surface, NULL, &surf_lock_desc,
      DDLOCK_WAIT | DDLOCK_NOSYSLOCK, NULL);
  if (hRes == DDERR_SURFACELOST) {
    IDirectDrawSurface7_Restore (surface->surface);
    goto lock;
  }
  surface->locked = TRUE;

  if (surf_lock_desc.lPitch != pitch) {
    GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
        "DDraw stride/pitch %ld isn't as expected value %d, let's continue allocating a system memory buffer.",
        surf_lock_desc.lPitch, pitch);

    /*Unlock the surface as we will change it to use system memory with a GStreamer compatible pitch */
    hRes = IDirectDrawSurface_Unlock (surface->surface, NULL);
    goto surface_pitch_bad;
  }
  GST_BUFFER_DATA (surface) = surf_lock_desc.lpSurface;
  GST_BUFFER_SIZE (surface) = surf_lock_desc.lPitch * surface->height;
  GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
      "allocating a surface of %d bytes (stride=%ld)\n", size,
      surf_lock_desc.lPitch);

surface_pitch_bad:
#else
  GST_BUFFER (surface)->malloc_data = g_malloc (size);
  GST_BUFFER_DATA (surface) = GST_BUFFER (surface)->malloc_data;
  GST_BUFFER_SIZE (surface) = size;
  surface->surface = NULL;
  GST_CAT_INFO_OBJECT (directdrawsink_debug, ddrawsink,
      "allocating a system memory buffer of %d bytes", size);

#endif

  /* Keep a ref to our sink */
  surface->ddrawsink = gst_object_ref (ddrawsink);

  return surface;
}

/* We are called from the finalize method of miniobject, the object will be
 * destroyed so we just have to clean our internal stuff */
static void
gst_directdraw_sink_surface_destroy (GstDirectDrawSink * ddrawsink,
    GstDDrawSurface * surface)
{
  g_return_if_fail (GST_IS_DIRECTDRAW_SINK (ddrawsink));

  /* Release our internal surface */
  if (surface->surface) {
    if (surface->locked) {
      IDirectDrawSurface7_Unlock (surface->surface, NULL);
      surface->locked = FALSE;
    }
    IDirectDrawSurface7_Release (surface->surface);
    surface->surface = NULL;
  }

  if (GST_BUFFER (surface)->malloc_data) {
    g_free (GST_BUFFER (surface)->malloc_data);
    GST_BUFFER (surface)->malloc_data = NULL;
  }

  if (!surface->ddrawsink) {
    goto no_sink;
  }

  /* Release the ref to our sink */
  surface->ddrawsink = NULL;
  gst_object_unref (ddrawsink);

  return;

no_sink:
  GST_WARNING ("no sink found in surface");
  return;
}

static gboolean
gst_directdraw_sink_surface_check (GstDirectDrawSink * ddrawsink,
    GstDDrawSurface * surface)
{
  if (!surface->surface)
    return TRUE;                /* system memory buffer */

  if (IDirectDrawSurface7_IsLost (surface->surface) == DD_OK) {
    /* no problem with this surface */
    return TRUE;
  } else {
    /* this surface was lost, try to restore it */
    if (IDirectDrawSurface7_Restore (ddrawsink->offscreen_surface) == DD_OK) {
      /* restore is done */
      GST_CAT_LOG_OBJECT (directdrawsink_debug, ddrawsink, "A surface from our"
          " bufferpool was restored after lost");
      return TRUE;
    }
  }

  return FALSE;
}

static void
gst_directdraw_sink_bufferpool_clear (GstDirectDrawSink * ddrawsink)
{
  g_mutex_lock (ddrawsink->pool_lock);
  while (ddrawsink->buffer_pool) {
    GstDDrawSurface *surface = ddrawsink->buffer_pool->data;

    ddrawsink->buffer_pool = g_slist_delete_link (ddrawsink->buffer_pool,
        ddrawsink->buffer_pool);
    gst_directdraw_sink_surface_destroy (ddrawsink, surface);
    gst_buffer_unref (GST_BUFFER_CAST (surface));
  }
  g_mutex_unlock (ddrawsink->pool_lock);
}

static void
gst_directdraw_sink_cleanup (GstDirectDrawSink * ddrawsink)
{
  /* Post quit message and wait for our event window thread */
  if (ddrawsink->video_window && ddrawsink->our_video_window)
    PostMessage (ddrawsink->video_window, WM_QUIT, 0, 0);

  if (ddrawsink->window_thread) {
    g_thread_join (ddrawsink->window_thread);
    ddrawsink->window_thread = NULL;
  }

  if (ddrawsink->buffer_pool) {
    gst_directdraw_sink_bufferpool_clear (ddrawsink);
    ddrawsink->buffer_pool = NULL;
  }

  if (ddrawsink->offscreen_surface) {
    IDirectDrawSurface7_Release (ddrawsink->offscreen_surface);
    ddrawsink->offscreen_surface = NULL;
  }

  if (ddrawsink->clipper) {
    IDirectDrawClipper_Release (ddrawsink->clipper);
    ddrawsink->clipper = NULL;
  }

  if (ddrawsink->primary_surface) {
    IDirectDrawSurface7_Release (ddrawsink->primary_surface);
    ddrawsink->primary_surface = NULL;
  }

  if (ddrawsink->ddraw_object) {
    IDirectDraw7_Release (ddrawsink->ddraw_object);
    ddrawsink->ddraw_object = NULL;
  }

  if (ddrawsink->last_buffer) {
    gst_buffer_unref (ddrawsink->last_buffer);
    ddrawsink->last_buffer = NULL;
  }

  ddrawsink->setup = FALSE;
}
