/* GStreamer * Copyright (C) 2005 Sebastien Moutte * Copyright (C) 2007 Pioneers of the Inevitable * * 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. * * * Example pipelines * |[ * gst-launch-0.10 -v videotestsrc ! directdrawsink * ]| a simple pipeline to test the sink * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstdirectdrawsink.h" GST_DEBUG_CATEGORY_STATIC (directdrawsink_debug); #define GST_CAT_DEFAULT directdrawsink_debug /* elementfactory information */ static const GstElementDetails gst_directdraw_sink_details = GST_ELEMENT_DETAILS ("Direct Draw Video Sink", "Sink/Video", "Output to a video card via Direct Draw", "Sebastien Moutte "); 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); 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) { g_assert (type == GST_TYPE_X_OVERLAY); return TRUE; } static void gst_directdraw_sink_interface_init (GstImplementsInterfaceClass * klass) { klass->supported = gst_directdraw_sink_interface_supported; } static void gst_directdraw_sink_set_window_id (GstXOverlay * overlay, ULONG window_id) { 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_id) { GST_OBJECT_UNLOCK (ddrawsink); return; } if (window_id) { 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_id; 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_id 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_xwindow_id = gst_directdraw_sink_set_window_id; iface->expose = gst_directdraw_sink_expose; } 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, }; g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE, &iface_info); g_type_add_interface_static (type, GST_TYPE_X_OVERLAY, &xoverlay_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); } 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 (element_class, &gst_directdraw_sink_details); 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; } 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 (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", &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", &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 (IDirectDrawSurface7_GetDC (ddrawsink->primary_surface, &hdc) == DD_OK) { HBRUSH brush = CreateSolidBrush (RGB (0, 0, 0)); /* 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); IDirectDrawSurface7_ReleaseDC (ddrawsink->primary_surface, 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); 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; 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)); } 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")) { gint 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. */ 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; } long FAR PASCAL WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { 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, NULL); 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_xwindow_id (GST_X_OVERLAY (ddrawsink), (gulong) 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; } 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 (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; }