/* GStreamer * Copyright (C) 2010-2011 David Hoyt * Copyright (C) 2010 Andoni Morales * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "d3dvideosink.h" #define IPC_SET_WINDOW 1 #define IDT_DEVICELOST 1 /* Provide access to data that will be shared among all instantiations of this element */ #define GST_D3DVIDEOSINK_SHARED_D3D_LOCK g_static_mutex_lock (&shared_d3d_lock); #define GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK g_static_mutex_unlock (&shared_d3d_lock); #define GST_D3DVIDEOSINK_SHARED_D3D_HOOK_LOCK g_static_mutex_lock (&shared_d3d_hook_lock); #define GST_D3DVIDEOSINK_SHARED_D3D_HOOK_UNLOCK g_static_mutex_unlock (&shared_d3d_hook_lock); typedef struct _GstD3DVideoSinkShared GstD3DVideoSinkShared; struct _GstD3DVideoSinkShared { LPDIRECT3D9 d3d; D3DCAPS9 d3dcaps; GList *element_list; gint32 element_count; gboolean device_lost; UINT_PTR device_lost_timer; GstD3DVideoSink *device_lost_sink; HWND hidden_window_handle; HANDLE hidden_window_created_signal; GThread *hidden_window_thread; GHashTable *hook_tbl; }; typedef struct _GstD3DVideoSinkHookData GstD3DVideoSinkHookData; struct _GstD3DVideoSinkHookData { HHOOK hook; HWND window_handle; DWORD thread_id; DWORD process_id; }; /* Holds our shared information */ static GstD3DVideoSinkShared shared; /* Define a shared lock to synchronize the creation/destruction of the d3d device */ static GStaticMutex shared_d3d_lock = G_STATIC_MUTEX_INIT; static GStaticMutex shared_d3d_dev_lock = G_STATIC_MUTEX_INIT; static GStaticMutex shared_d3d_hook_lock = G_STATIC_MUTEX_INIT; /* Hold a reference to our dll's HINSTANCE */ static HINSTANCE g_hinstDll = NULL; typedef struct _IPCData IPCData; struct _IPCData { HWND hwnd; LONG_PTR wnd_proc; }; /* Holds data that may be used to communicate across processes */ /*static IPCData ipc_data;*/ /*static COPYDATASTRUCT ipc_cds;*/ GST_DEBUG_CATEGORY (d3dvideosink_debug); #define GST_CAT_DEFAULT d3dvideosink_debug static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("{ YUY2, UYVY, YV12, I420, NV12 }") ";" GST_VIDEO_CAPS_BGRx ";" GST_VIDEO_CAPS_BGRA) ); static void gst_d3dvideosink_init_interfaces (GType type); GST_BOILERPLATE_FULL (GstD3DVideoSink, gst_d3dvideosink, GstVideoSink, GST_TYPE_VIDEO_SINK, gst_d3dvideosink_init_interfaces); enum { PROP_0, PROP_KEEP_ASPECT_RATIO, PROP_PIXEL_ASPECT_RATIO, PROP_ENABLE_NAVIGATION_EVENTS, PROP_LAST }; /* GObject methods */ static void gst_d3dvideosink_finalize (GObject * gobject); static void gst_d3dvideosink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_d3dvideosink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); /* GstElement methods */ static GstStateChangeReturn gst_d3dvideosink_change_state (GstElement * element, GstStateChange transition); /* GstBaseSink methods */ static gboolean gst_d3dvideosink_start (GstBaseSink * bsink); static gboolean gst_d3dvideosink_stop (GstBaseSink * bsink); static gboolean gst_d3dvideosink_set_caps (GstBaseSink * bsink, GstCaps * caps); static GstCaps *gst_d3dvideosink_get_caps (GstBaseSink * bsink); static GstFlowReturn gst_d3dvideosink_show_frame (GstVideoSink * sink, GstBuffer * buffer); /* GstXOverlay methods */ static void gst_d3dvideosink_set_window_handle (GstXOverlay * overlay, guintptr window_id); static void gst_d3dvideosink_expose (GstXOverlay * overlay); /* GstNavigation methods */ static void gst_d3dvideosink_navigation_send_event (GstNavigation * navigation, GstStructure * structure); /* WndProc methods */ LRESULT APIENTRY WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); LRESULT APIENTRY SharedHiddenWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static void gst_d3dvideosink_wnd_proc (GstD3DVideoSink * sink, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); /* HookProc methods */ LRESULT APIENTRY WndProcHook (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK gst_d3dvideosink_hook_proc (int nCode, WPARAM wParam, LPARAM lParam); /* Paint/update methods */ static void gst_d3dvideosink_update (GstBaseSink * bsink); static gboolean gst_d3dvideosink_refresh (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_update_all (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_refresh_all (GstD3DVideoSink * sink); static void gst_d3dvideosink_stretch (GstD3DVideoSink * sink, LPDIRECT3DSURFACE9 backBuffer); /* Misc methods */ BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad); static void gst_d3dvideosink_remove_window_for_renderer (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_initialize_direct3d (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_initialize_d3d_device (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_resize_swap_chain (GstD3DVideoSink * sink, gint width, gint height); static gboolean gst_d3dvideosink_notify_device_init (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_notify_device_lost (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_notify_device_reset (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_notify_device_resize (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_device_lost (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_release_d3d_device (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_release_direct3d (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_window_size (GstD3DVideoSink * sink, gint * width, gint * height); static gboolean gst_d3dvideosink_direct3d_supported (GstD3DVideoSink * sink); static gboolean gst_d3dvideosink_shared_hidden_window_thread (GstD3DVideoSink * sink); static void gst_d3dvideosink_flush_gpu (GstD3DVideoSink * sink); static void gst_d3dvideosink_hook_window_for_renderer (GstD3DVideoSink * sink); static void gst_d3dvideosink_unhook_window_for_renderer (GstD3DVideoSink * sink); static void gst_d3dvideosink_unhook_all_windows (void); static void gst_d3dvideosink_log_debug (const gchar * file, const gchar * function, gint line, const gchar * format, va_list args); static void gst_d3dvideosink_log_warning (const gchar * file, const gchar * function, gint line, const gchar * format, va_list args); static void gst_d3dvideosink_log_error (const gchar * file, const gchar * function, gint line, const gchar * format, va_list args); static void gst_d3dvideosink_set_window_for_renderer (GstD3DVideoSink * sink); static DirectXInitParams directx_init_params = { gst_d3dvideosink_log_debug, gst_d3dvideosink_log_warning, gst_d3dvideosink_log_error }; /* TODO: event, preroll, buffer_alloc? * buffer_alloc won't generally be all that useful because the renderers require a * different stride to GStreamer's implicit values. */ BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad) { switch (fdwReason) { case DLL_PROCESS_ATTACH: g_hinstDll = hinstDll; break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: gst_d3dvideosink_unhook_all_windows (); break; } return TRUE; } static gboolean gst_d3dvideosink_interface_supported (GstImplementsInterface * iface, GType type) { return (type == GST_TYPE_X_OVERLAY || type == GST_TYPE_NAVIGATION); } static void gst_d3dvideosink_interface_init (GstImplementsInterfaceClass * klass) { klass->supported = gst_d3dvideosink_interface_supported; } static void gst_d3dvideosink_xoverlay_interface_init (GstXOverlayClass * iface) { iface->set_window_handle = gst_d3dvideosink_set_window_handle; iface->expose = gst_d3dvideosink_expose; } static void gst_d3dvideosink_navigation_interface_init (GstNavigationInterface * iface) { iface->send_event = gst_d3dvideosink_navigation_send_event; } static void gst_d3dvideosink_init_interfaces (GType type) { static const GInterfaceInfo iface_info = { (GInterfaceInitFunc) gst_d3dvideosink_interface_init, NULL, NULL }; static const GInterfaceInfo xoverlay_info = { (GInterfaceInitFunc) gst_d3dvideosink_xoverlay_interface_init, NULL, NULL }; static const GInterfaceInfo navigation_info = { (GInterfaceInitFunc) gst_d3dvideosink_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); GST_DEBUG_CATEGORY_INIT (d3dvideosink_debug, "d3dvideosink", 0, "Direct3D video sink"); } static void gst_d3dvideosink_base_init (gpointer klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); gst_element_class_add_static_pad_template (element_class, &sink_template); gst_element_class_set_details_simple (element_class, "Direct3D video sink", "Sink/Video", "Display data using a Direct3D video renderer", "David Hoyt "); } static void gst_d3dvideosink_class_init (GstD3DVideoSinkClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; GstBaseSinkClass *gstbasesink_class; GstVideoSinkClass *gstvideosink_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstbasesink_class = (GstBaseSinkClass *) klass; gstvideosink_class = (GstVideoSinkClass *) klass; gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_d3dvideosink_finalize); gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_d3dvideosink_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_d3dvideosink_get_property); gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_d3dvideosink_change_state); gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_d3dvideosink_get_caps); gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_d3dvideosink_set_caps); gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_d3dvideosink_start); gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_d3dvideosink_stop); /*gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_d3dvideosink_unlock); */ /*gstbasesink_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_d3dvideosink_unlock_stop); */ gstvideosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_d3dvideosink_show_frame); /* Add properties */ 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, (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PIXEL_ASPECT_RATIO, g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio", "The pixel aspect ratio of the device", "1/1", (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ENABLE_NAVIGATION_EVENTS, g_param_spec_boolean ("enable-navigation-events", "Enable navigation events", "When enabled, navigation events are sent upstream", TRUE, (GParamFlags) G_PARAM_READWRITE)); /* Initialize DirectX abstraction */ GST_DEBUG ("Initializing DirectX abstraction layer"); directx_initialize (&directx_init_params); /* Initialize DirectX API */ if (!directx_initialize_best_available_api ()) GST_DEBUG ("Unable to initialize DirectX"); /* Determine DirectX version */ klass->directx_api = directx_get_best_available_api (); klass->directx_version = (klass->directx_api != NULL ? klass->directx_api->version : DIRECTX_VERSION_UNKNOWN); klass->is_directx_supported = directx_is_supported (); } static void gst_d3dvideosink_clear (GstD3DVideoSink * sink) { sink->enable_navigation_events = TRUE; sink->keep_aspect_ratio = FALSE; sink->window_closed = FALSE; sink->window_handle = NULL; sink->is_new_window = FALSE; sink->is_hooked = FALSE; } static void gst_d3dvideosink_init (GstD3DVideoSink * sink, GstD3DVideoSinkClass * klass) { gst_d3dvideosink_clear (sink); sink->d3d_swap_chain_lock = g_mutex_new (); sink->par = g_new0 (GValue, 1); g_value_init (sink->par, GST_TYPE_FRACTION); gst_value_set_fraction (sink->par, 1, 1); /* TODO: Copied from GstVideoSink; should we use that as base class? */ /* 20ms is more than enough, 80-130ms is noticable */ gst_base_sink_set_max_lateness (GST_BASE_SINK (sink), 20 * GST_MSECOND); gst_base_sink_set_qos_enabled (GST_BASE_SINK (sink), TRUE); } static void gst_d3dvideosink_finalize (GObject * gobject) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (gobject); if (sink->par) { g_free (sink->par); sink->par = NULL; } g_mutex_free (sink->d3d_swap_chain_lock); sink->d3d_swap_chain_lock = NULL; G_OBJECT_CLASS (parent_class)->finalize (gobject); } static void gst_d3dvideosink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (object); switch (prop_id) { case PROP_ENABLE_NAVIGATION_EVENTS: sink->enable_navigation_events = g_value_get_boolean (value); break; case PROP_KEEP_ASPECT_RATIO: sink->keep_aspect_ratio = g_value_get_boolean (value); break; case PROP_PIXEL_ASPECT_RATIO: g_free (sink->par); sink->par = g_new0 (GValue, 1); g_value_init (sink->par, GST_TYPE_FRACTION); if (!g_value_transform (value, sink->par)) { g_warning ("Could not transform string to aspect ratio"); gst_value_set_fraction (sink->par, 1, 1); } GST_DEBUG_OBJECT (sink, "set PAR to %d/%d", gst_value_get_fraction_numerator (sink->par), gst_value_get_fraction_denominator (sink->par)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_d3dvideosink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (object); switch (prop_id) { case PROP_ENABLE_NAVIGATION_EVENTS: g_value_set_boolean (value, sink->enable_navigation_events); break; case PROP_KEEP_ASPECT_RATIO: g_value_set_boolean (value, sink->keep_aspect_ratio); break; case PROP_PIXEL_ASPECT_RATIO: g_value_transform (sink->par, value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstCaps * gst_d3dvideosink_get_caps (GstBaseSink * basesink) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (basesink); GstCaps *caps = gst_caps_new_empty (); const GstCaps *tempCaps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (sink)); /* restrict caps based on the hw capabilities */ if (shared.d3d) { D3DDISPLAYMODE d3ddm; if (FAILED (IDirect3D9_GetAdapterDisplayMode (shared.d3d, D3DADAPTER_DEFAULT, &d3ddm))) { GST_WARNING ("Unable to request adapter display mode"); gst_caps_unref (caps); caps = gst_caps_copy (tempCaps); } else { gint i; GstCaps *c = gst_caps_normalize (tempCaps); for (i = 0; i < gst_caps_get_size (c); i++) { D3DFORMAT d3dfourcc = 0; GstStructure *stru = gst_caps_get_structure (c, i); if (!gst_structure_has_name (stru, "video/x-raw-rgb")) { gst_structure_get_fourcc (stru, "format", (guint32 *) & d3dfourcc); switch (d3dfourcc) { case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): case GST_MAKE_FOURCC ('I', '4', '2', '0'): d3dfourcc = (D3DFORMAT) MAKEFOURCC ('Y', 'V', '1', '2'); break; } if (d3dfourcc && SUCCEEDED (IDirect3D9_CheckDeviceFormat (shared.d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3ddm.Format, 0, D3DRTYPE_SURFACE, d3dfourcc))) { /* hw supports this format */ gst_caps_append (caps, gst_caps_copy_nth (c, i)); } } else { /* rgb formats */ gst_caps_append (caps, gst_caps_copy_nth (c, i)); } } gst_caps_unref (c); } } else { gst_caps_unref (caps); caps = gst_caps_copy (tempCaps); } return caps; } static void gst_d3dvideosink_close_window (GstD3DVideoSink * sink) { if (!sink || !sink->window_handle) return; if (!sink->is_new_window) { gst_d3dvideosink_remove_window_for_renderer (sink); return; } SendMessage (sink->window_handle, WM_CLOSE, (WPARAM) NULL, (WPARAM) NULL); g_thread_join (sink->window_thread); sink->is_new_window = FALSE; } static gboolean gst_d3dvideosink_create_shared_hidden_window (GstD3DVideoSink * sink) { GST_DEBUG ("Creating Direct3D hidden window"); shared.hidden_window_created_signal = CreateSemaphore (NULL, 0, 1, NULL); if (shared.hidden_window_created_signal == NULL) goto failed; shared.hidden_window_thread = g_thread_create ((GThreadFunc) gst_d3dvideosink_shared_hidden_window_thread, sink, TRUE, NULL); /* wait maximum 60 seconds for window to be created */ if (WaitForSingleObject (shared.hidden_window_created_signal, 60000) != WAIT_OBJECT_0) goto failed; CloseHandle (shared.hidden_window_created_signal); if (!shared.d3ddev) goto failed; GST_DEBUG ("Successfully created Direct3D hidden window, handle: %p", shared.hidden_window_handle); return (shared.hidden_window_handle != NULL); failed: CloseHandle (shared.hidden_window_created_signal); GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("Error creating Direct3D hidden window"), (NULL)); return FALSE; } static gboolean gst_d3dvideosink_shared_hidden_window_created (GstD3DVideoSink * sink) { /* Should only be called from the shared window thread. */ ReleaseSemaphore (shared.hidden_window_created_signal, 1, NULL); return TRUE; } static gboolean gst_d3dvideosink_shared_hidden_window_thread (GstD3DVideoSink * sink) { WNDCLASS WndClass; HWND hWnd; MSG msg; memset (&WndClass, 0, sizeof (WNDCLASS)); WndClass.hInstance = GetModuleHandle (NULL); WndClass.lpszClassName = TEXT ("GST-Shared-Hidden-D3DSink"); WndClass.lpfnWndProc = SharedHiddenWndProc; if (!RegisterClass (&WndClass)) { GST_ERROR ("Unable to register Direct3D hidden window class"); return FALSE; } hWnd = CreateWindowEx (0, WndClass.lpszClassName, TEXT ("GStreamer Direct3D hidden window"), WS_POPUP, 0, 0, 1, 1, HWND_MESSAGE, NULL, WndClass.hInstance, sink); if (hWnd == NULL) { GST_ERROR_OBJECT (sink, "Failed to create Direct3D hidden window"); goto error; } GST_DEBUG ("Direct3D hidden window handle: %p", hWnd); shared.hidden_window_handle = hWnd; shared.device_lost_timer = 0; gst_d3dvideosink_shared_hidden_window_created (sink); GST_DEBUG ("Entering Direct3D hidden window message loop"); /* start message loop processing */ while (TRUE) { while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } if (msg.message == WM_QUIT || msg.message == WM_CLOSE) break; } GST_DEBUG ("Leaving Direct3D hidden window message loop"); /*success:*/ /* Kill the device lost timer if it's running */ if (shared.device_lost_timer != 0) KillTimer (hWnd, shared.device_lost_timer); UnregisterClass (WndClass.lpszClassName, WndClass.hInstance); shared.device_lost_timer = 0; return TRUE; error: /* Kill the device lost timer if it's running */ if (shared.device_lost_timer != 0) KillTimer (hWnd, shared.device_lost_timer); if (hWnd) DestroyWindow (hWnd); UnregisterClass (WndClass.lpszClassName, WndClass.hInstance); shared.hidden_window_handle = NULL; shared.device_lost_timer = 0; ReleaseSemaphore (shared.hidden_window_created_signal, 1, NULL); return FALSE; } LRESULT APIENTRY SharedHiddenWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { GstD3DVideoSink *sink; /* lParam holds pointer to the sink. */ sink = (GstD3DVideoSink *) lParam; switch (message) { case WM_DIRECTX_D3D_INIT_DEVICE: { shared.device_lost_sink = NULL; GST_DEBUG ("Initializing Direct3D"); gst_d3dvideosink_initialize_d3d_device (sink); GST_DEBUG ("Direct3D initialization complete"); break; } case WM_DIRECTX_D3D_INIT_DEVICELOST: { if (!shared.device_lost) { shared.device_lost = TRUE; shared.device_lost_sink = sink; /* Handle device lost by creating a timer and posting WM_D3D_DEVICELOST twice a second */ /* Create a timer to periodically check the d3d device and attempt to recreate it */ shared.device_lost_timer = SetTimer (hWnd, IDT_DEVICELOST, 500, NULL); /* Try it once immediately */ SendMessage (hWnd, WM_DIRECTX_D3D_DEVICELOST, 0, (LPARAM) sink); } break; } case WM_TIMER: { /* Did we receive a message to check if the device is available again? */ if (wParam == IDT_DEVICELOST) { /* This will synchronously call SharedHiddenWndProc() because this thread is the one that created the window. */ SendMessage (hWnd, WM_DIRECTX_D3D_DEVICELOST, 0, (LPARAM) shared.device_lost_sink); return 0; } break; } case WM_DIRECTX_D3D_DEVICELOST: { return gst_d3dvideosink_device_lost (sink); } case WM_DIRECTX_D3D_END_DEVICELOST: { if (shared.device_lost) { /* gst_d3dvideosink_notify_device_reset() sends this message. */ if (shared.device_lost_timer != 0) KillTimer (hWnd, shared.device_lost_timer); shared.device_lost_timer = 0; shared.device_lost = FALSE; /* Refresh the video with the last buffer */ gst_d3dvideosink_update_all (sink); /* Then redraw just in case we don't have a last buffer */ gst_d3dvideosink_refresh_all (sink); } shared.device_lost_sink = NULL; break; } case WM_DESTROY: { PostQuitMessage (0); return 0; } case WM_DIRECTX_D3D_RESIZE: { return gst_d3dvideosink_device_lost (sink); } } return DefWindowProc (hWnd, message, wParam, lParam); } static void gst_d3dvideosink_close_shared_hidden_window (GstD3DVideoSink * sink) { if (!shared.hidden_window_handle) return; SendMessage (shared.hidden_window_handle, WM_CLOSE, (WPARAM) NULL, (WPARAM) NULL); if (shared.hidden_window_thread) { g_thread_join (shared.hidden_window_thread); shared.hidden_window_thread = NULL; } shared.hidden_window_handle = NULL; GST_DEBUG ("Successfully closed Direct3D hidden window"); } /* WNDPROC for application-supplied windows */ LRESULT APIENTRY WndProcHook (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { /* Handle certain actions specially on the window passed to us. * Then forward back to the original window. */ GstD3DVideoSink *sink = (GstD3DVideoSink *) GetProp (hWnd, TEXT ("GstD3DVideoSink")); switch (message) { case WM_ERASEBKGND: return TRUE; case WM_COPYDATA: { gst_d3dvideosink_wnd_proc (sink, hWnd, message, wParam, lParam); return TRUE; } case WM_PAINT: { LRESULT ret; ret = CallWindowProc (sink->prevWndProc, hWnd, message, wParam, lParam); /* Call this afterwards to ensure that our paint happens last */ gst_d3dvideosink_wnd_proc (sink, hWnd, message, wParam, lParam); return ret; } default: { /* Check it */ gst_d3dvideosink_wnd_proc (sink, hWnd, message, wParam, lParam); return CallWindowProc (sink->prevWndProc, hWnd, message, wParam, lParam); } } } /* WndProc for our default window, if the application didn't supply one */ LRESULT APIENTRY WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { GstD3DVideoSink *sink; if (message == WM_CREATE) { /* lParam holds a pointer to a CREATESTRUCT instance which in turn holds the parameter used when creating the window. */ GstD3DVideoSink *sink = (GstD3DVideoSink *) ((LPCREATESTRUCT) lParam)->lpCreateParams; /* In our case, this is a pointer to the sink. So we immediately attach it for use in subsequent calls. */ SetWindowLongPtr (hWnd, GWLP_USERDATA, (LONG_PTR) sink); /* signal application we created a window */ gst_x_overlay_got_window_handle (GST_X_OVERLAY (sink), (guintptr) hWnd); } sink = (GstD3DVideoSink *) GetWindowLongPtr (hWnd, GWLP_USERDATA); gst_d3dvideosink_wnd_proc (sink, hWnd, message, wParam, lParam); switch (message) { case WM_ERASEBKGND: case WM_COPYDATA: return TRUE; case WM_DESTROY: { PostQuitMessage (0); return 0; } } return DefWindowProc (hWnd, message, wParam, lParam); } static void gst_d3dvideosink_wnd_proc (GstD3DVideoSink * sink, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COPYDATA: { PCOPYDATASTRUCT p_ipc_cds; p_ipc_cds = (PCOPYDATASTRUCT) lParam; switch (p_ipc_cds->dwData) { case IPC_SET_WINDOW: { IPCData *p_ipc_data; p_ipc_data = (IPCData *) p_ipc_cds->dwData; GST_DEBUG ("Received IPC call to subclass the window handler"); sink->window_handle = p_ipc_data->hwnd; sink->prevWndProc = (WNDPROC) SetWindowLongPtr (sink->window_handle, GWLP_WNDPROC, (LONG_PTR) p_ipc_data->wnd_proc); break; } } break; } case WM_PAINT: { gst_d3dvideosink_refresh (sink); break; } case WM_CLOSE: case WM_DESTROY: { sink->window_closed = TRUE; //GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND, ("Output window was closed"), (NULL)); break; } case WM_CHAR: case WM_KEYDOWN: case WM_KEYUP: { if (!sink->enable_navigation_events) break; } { gunichar2 wcrep[128]; if (GetKeyNameTextW (lParam, (LPWSTR) 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 (sink), "key-press", utfrep); if (message == WM_CHAR || message == WM_KEYUP) gst_navigation_send_key_event (GST_NAVIGATION (sink), "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: { if (!sink->enable_navigation_events) break; } { 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; break; } x = LOWORD (lParam); y = HIWORD (lParam); if (button == 0) { GST_DEBUG_OBJECT (sink, "Mouse moved to %dx%d", x, y); } else GST_DEBUG_OBJECT (sink, "Mouse button %d pressed at %dx%d", button, x, y); if (button < 4) gst_navigation_send_mouse_event (GST_NAVIGATION (sink), action, button, x, y); break; } } } static gpointer gst_d3dvideosink_window_thread (GstD3DVideoSink * sink) { WNDCLASS WndClass; int width, height; int offx, offy; DWORD exstyle, style; HWND video_window; RECT rect; int screenwidth; int screenheight; MSG msg; memset (&WndClass, 0, sizeof (WNDCLASS)); WndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW; WndClass.hInstance = GetModuleHandle (NULL); WndClass.lpszClassName = TEXT ("GST-D3DSink"); WndClass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); WndClass.hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.hIcon = LoadIcon (NULL, IDI_APPLICATION); WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.lpfnWndProc = WndProc; RegisterClass (&WndClass); /* By default, create a normal top-level window, the size of the video. */ /* GST_VIDEO_SINK_WIDTH() is the aspect-ratio-corrected size of the video. */ /* GetSystemMetrics() returns the width of the dialog's border (doubled b/c of left and right borders). */ width = GST_VIDEO_SINK_WIDTH (sink) + GetSystemMetrics (SM_CXSIZEFRAME) * 2; height = GST_VIDEO_SINK_HEIGHT (sink) + GetSystemMetrics (SM_CYCAPTION) + (GetSystemMetrics (SM_CYSIZEFRAME) * 2); SystemParametersInfo (SPI_GETWORKAREA, 0, &rect, 0); screenwidth = rect.right - rect.left; screenheight = rect.bottom - rect.top; offx = rect.left; offy = rect.top; /* Make it fit into the screen without changing the aspect ratio. */ if (width > screenwidth) { double ratio = (double) screenwidth / (double) width; width = screenwidth; height = (int) (height * ratio); } if (height > screenheight) { double ratio = (double) screenheight / (double) height; height = screenheight; width = (int) (width * ratio); } style = WS_OVERLAPPEDWINDOW; /* Normal top-level window */ exstyle = 0; video_window = CreateWindowEx (exstyle, TEXT ("GST-D3DSink"), TEXT ("GStreamer Direct3D sink default window"), style, offx, offy, width, height, NULL, NULL, WndClass.hInstance, sink); if (video_window == NULL) { GST_ERROR_OBJECT (sink, "Failed to create window"); return NULL; } sink->is_new_window = TRUE; sink->window_handle = video_window; /* Now show the window, as appropriate */ ShowWindow (video_window, SW_SHOWNORMAL); /* Trigger the initial paint of the window */ UpdateWindow (video_window); ReleaseSemaphore (sink->window_created_signal, 1, NULL); /* start message loop processing our default window messages */ while (TRUE) { //while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } if (msg.message == WM_QUIT || msg.message == WM_CLOSE) break; } UnregisterClass (WndClass.lpszClassName, WndClass.hInstance); sink->window_handle = NULL; return NULL; /*destroy_window:*/ /* if (video_window) { */ /* DestroyWindow(video_window); */ /* UnregisterClass(WndClass.lpszClassName, WndClass.hInstance); */ /* } */ /* sink->window_handle = NULL; */ /* ReleaseSemaphore (sink->window_created_signal, 1, NULL); */ /* return NULL; */ } static gboolean gst_d3dvideosink_create_default_window (GstD3DVideoSink * sink) { if (shared.device_lost) return FALSE; sink->window_created_signal = CreateSemaphore (NULL, 0, 1, NULL); if (sink->window_created_signal == NULL) goto failed; sink->window_thread = g_thread_create ((GThreadFunc) gst_d3dvideosink_window_thread, sink, TRUE, NULL); /* wait maximum 10 seconds for window to be created */ if (WaitForSingleObject (sink->window_created_signal, 10000) != WAIT_OBJECT_0) goto failed; CloseHandle (sink->window_created_signal); return (sink->window_handle != NULL); failed: CloseHandle (sink->window_created_signal); GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("Error creating our default window"), (NULL)); return FALSE; } static void gst_d3dvideosink_set_window_handle (GstXOverlay * overlay, guintptr window_id) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (overlay); HWND hWnd = (HWND) window_id; if (hWnd == sink->window_handle) { GST_DEBUG ("Window already set"); return; } /* If we're already playing/paused, then we need to lock the swap chain, and recreate it with the new window. */ if (sink->d3ddev != NULL) { /* Close our existing window if there is one */ gst_d3dvideosink_close_window (sink); /* Save our window id */ sink->window_handle = hWnd; gst_d3dvideosink_set_window_for_renderer (sink); gst_d3dvideosink_notify_device_resize (sink); } else { sink->window_handle = hWnd; } /*success:*/ GST_DEBUG_OBJECT (sink, "Direct3D window id successfully changed to %p", hWnd); gst_d3dvideosink_update (GST_BASE_SINK_CAST (sink)); return; /*error:*/ /* GST_DEBUG("Error attempting to change the window id for sink %d to %d", sink, hWnd); */ /* GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK(sink); */ /* return; */ } /* Hook for out-of-process rendering */ LRESULT CALLBACK gst_d3dvideosink_hook_proc (int nCode, WPARAM wParam, LPARAM lParam) { //LPCWPSTRUCT p = (LPCWPSTRUCT)lParam; // //if (p && p->hwnd) // WndProcHook(p->hwnd, p->message, p->wParam, p->lParam); return CallNextHookEx (NULL, nCode, wParam, lParam); } static void gst_d3dvideosink_set_window_for_renderer (GstD3DVideoSink * sink) { WNDPROC currWndProc; /* Application has requested a specific window ID */ sink->is_new_window = FALSE; currWndProc = (WNDPROC) GetWindowLongPtr (sink->window_handle, GWLP_WNDPROC); if (sink->prevWndProc != currWndProc && currWndProc != WndProcHook) sink->prevWndProc = (WNDPROC) SetWindowLongPtr (sink->window_handle, GWLP_WNDPROC, (LONG_PTR) WndProcHook); /* Allows us to pick up the video sink inside the msg handler */ SetProp (sink->window_handle, TEXT ("GstD3DVideoSink"), sink); if (!(sink->prevWndProc)) { /* If we were unable to set the window procedure, it's possible we're attempting to render into the */ /* window from a separate process. In that case, we need to use a windows hook to see the messages */ /* going to the window we're drawing on. We must take special care that our hook is properly removed */ /* when we're done. */ GST_DEBUG ("Unable to set window procedure. Error: %s", g_win32_error_message (GetLastError ())); GST_D3DVIDEOSINK_SHARED_D3D_HOOK_LOCK gst_d3dvideosink_hook_window_for_renderer (sink); GST_D3DVIDEOSINK_SHARED_D3D_HOOK_UNLOCK} else { GST_DEBUG ("Set wndproc to %p from %p", WndProcHook, sink->prevWndProc); GST_DEBUG ("Set renderer window to %p", sink->window_handle); } sink->is_new_window = FALSE; } static HHOOK gst_d3dvideosink_find_hook (DWORD pid, DWORD tid) { HWND key; GHashTableIter iter; GstD3DVideoSinkHookData *value; if (!shared.hook_tbl) return NULL; g_hash_table_iter_init (&iter, shared.hook_tbl); while (g_hash_table_iter_next (&iter, (gpointer) & key, (gpointer) & value)) { if (value && value->process_id == pid && value->thread_id == tid) return value->hook; } return NULL; } static GstD3DVideoSinkHookData * gst_d3dvideosink_hook_data (HWND window_id) { if (!shared.hook_tbl) return NULL; return (GstD3DVideoSinkHookData *) g_hash_table_lookup (shared.hook_tbl, window_id); } static GstD3DVideoSinkHookData * gst_d3dvideosink_register_hook_data (HWND window_id) { GstD3DVideoSinkHookData *data; if (!shared.hook_tbl) shared.hook_tbl = g_hash_table_new (NULL, NULL); data = (GstD3DVideoSinkHookData *) g_hash_table_lookup (shared.hook_tbl, window_id); if (!data) { data = (GstD3DVideoSinkHookData *) g_malloc (sizeof (GstD3DVideoSinkHookData)); memset (data, 0, sizeof (GstD3DVideoSinkHookData)); g_hash_table_insert (shared.hook_tbl, window_id, data); } return data; } static gboolean gst_d3dvideosink_unregister_hook_data (HWND window_id) { GstD3DVideoSinkHookData *data; if (!shared.hook_tbl) return FALSE; data = (GstD3DVideoSinkHookData *) g_hash_table_lookup (shared.hook_tbl, window_id); if (!data) return TRUE; if (g_hash_table_remove (shared.hook_tbl, window_id)) g_free (data); return TRUE; } static void gst_d3dvideosink_hook_window_for_renderer (GstD3DVideoSink * sink) { /* Ensure that our window hook isn't already installed. */ if (!sink->is_new_window && !sink->is_hooked && sink->window_handle) { DWORD pid; DWORD tid; GST_DEBUG ("Attempting to apply a windows hook in process %lu.", GetCurrentProcessId ()); /* Get thread id of the window in question. */ tid = GetWindowThreadProcessId (sink->window_handle, &pid); if (tid) { HHOOK hook; GstD3DVideoSinkHookData *data; /* Only apply a hook if there's not one already there. It's possible this is the case if there are multiple */ /* embedded windows that we're hooking inside of the same dialog/thread. */ hook = gst_d3dvideosink_find_hook (pid, tid); data = gst_d3dvideosink_register_hook_data (sink->window_handle); if (data && !hook) { GST_DEBUG ("No other hooks exist for pid %lu and tid %lu. Attempting to add one.", pid, tid); hook = SetWindowsHookEx (WH_CALLWNDPROCRET, gst_d3dvideosink_hook_proc, g_hinstDll, tid); } sink->is_hooked = (hook ? TRUE : FALSE); if (sink->is_hooked) { data->hook = hook; data->process_id = pid; data->thread_id = tid; data->window_handle = sink->window_handle; PostThreadMessage (tid, WM_NULL, 0, 0); GST_DEBUG ("Window successfully hooked. GetLastError() returned: %s", g_win32_error_message (GetLastError ())); } else { /* Ensure that we clean up any allocated memory. */ if (data) gst_d3dvideosink_unregister_hook_data (sink->window_handle); GST_DEBUG ("Unable to hook the window. The system provided error was: %s", g_win32_error_message (GetLastError ())); } } } } static void gst_d3dvideosink_unhook_window_for_renderer (GstD3DVideoSink * sink) { if (!sink->is_new_window && sink->is_hooked && sink->window_handle) { GstD3DVideoSinkHookData *data; GST_DEBUG ("Unhooking a window in process %lu.", GetCurrentProcessId ()); data = gst_d3dvideosink_hook_data (sink->window_handle); if (data) { DWORD pid; DWORD tid; HHOOK hook; /* Save off a temp ref to the data */ hook = data->hook; tid = data->thread_id; pid = data->process_id; /* Free the memory */ if (gst_d3dvideosink_unregister_hook_data (sink->window_handle)) { /* Check if there's anyone else who still has the hook. If so, then we do nothing. */ /* If not, then go ahead and unhook. */ if (gst_d3dvideosink_find_hook (pid, tid)) { UnhookWindowsHookEx (hook); GST_DEBUG ("Unhooked the window for process %lu and thread %lu.", pid, tid); } } } sink->is_hooked = FALSE; GST_DEBUG ("Window successfully unhooked in process %lu.", GetCurrentProcessId ()); } } static void gst_d3dvideosink_unhook_all_windows (void) { /* Unhook all windows that may be currently hooked. This is mainly a precaution in case */ /* a wayward process doesn't properly set state back to NULL (which would remove the hook). */ GST_D3DVIDEOSINK_SHARED_D3D_LOCK GST_D3DVIDEOSINK_SHARED_D3D_HOOK_LOCK { GList *item; GstD3DVideoSink *s; GST_DEBUG ("Attempting to unhook all windows for process %lu", GetCurrentProcessId ()); for (item = g_list_first (shared.element_list); item; item = item->next) { s = (GstD3DVideoSink *) item->data; gst_d3dvideosink_unhook_window_for_renderer (s); } } GST_D3DVIDEOSINK_SHARED_D3D_HOOK_UNLOCK GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK} static void gst_d3dvideosink_remove_window_for_renderer (GstD3DVideoSink * sink) { //GST_D3DVIDEOSINK_SHARED_D3D_LOCK //GST_D3DVIDEOSINK_SWAP_CHAIN_LOCK(sink); { GST_DEBUG ("Removing custom rendering window procedure"); if (!sink->is_new_window && sink->window_handle) { WNDPROC currWndProc; /* Retrieve current msg handler */ currWndProc = (WNDPROC) GetWindowLongPtr (sink->window_handle, GWLP_WNDPROC); /* Return control of application window */ if (sink->prevWndProc != NULL && currWndProc == WndProcHook) { SetWindowLongPtr (sink->window_handle, GWLP_WNDPROC, (LONG_PTR) sink->prevWndProc); sink->prevWndProc = NULL; sink->window_handle = NULL; sink->is_new_window = FALSE; } } GST_D3DVIDEOSINK_SHARED_D3D_HOOK_LOCK gst_d3dvideosink_unhook_window_for_renderer (sink); GST_D3DVIDEOSINK_SHARED_D3D_HOOK_UNLOCK /* Remove the property associating our sink with the window */ RemoveProp (sink->window_handle, TEXT ("GstD3DVideoSink")); } //GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK(sink); //GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK } static void gst_d3dvideosink_prepare_window (GstD3DVideoSink * sink) { /* Give the app a last chance to supply a window id */ if (!sink->window_handle) { gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (sink)); } /* If the app supplied one, use it. Otherwise, go ahead * and create (and use) our own window */ if (sink->window_handle) { gst_d3dvideosink_set_window_for_renderer (sink); } else { gst_d3dvideosink_create_default_window (sink); } gst_d3dvideosink_notify_device_init (sink); } static GstStateChangeReturn gst_d3dvideosink_change_state (GstElement * element, GstStateChange transition) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (element); GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (!gst_d3dvideosink_initialize_direct3d (sink)) return GST_STATE_CHANGE_FAILURE; break; case GST_STATE_CHANGE_READY_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: break; case GST_STATE_CHANGE_READY_TO_NULL: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: gst_d3dvideosink_remove_window_for_renderer (sink); break; case GST_STATE_CHANGE_READY_TO_NULL: gst_d3dvideosink_release_direct3d (sink); gst_d3dvideosink_clear (sink); break; case GST_STATE_CHANGE_NULL_TO_READY: break; case GST_STATE_CHANGE_READY_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: break; } return ret; } static gboolean gst_d3dvideosink_start (GstBaseSink * bsink) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (bsink); /* Determine if Direct 3D is supported */ return gst_d3dvideosink_direct3d_supported (sink); } static gboolean gst_d3dvideosink_set_caps (GstBaseSink * bsink, GstCaps * caps) { GstD3DVideoSink *sink; GstCaps *sink_caps; gint video_width, video_height; gint video_par_n, video_par_d; /* video's PAR */ gint display_par_n, display_par_d; /* display's PAR */ gint fps_n, fps_d; guint num, den; sink = GST_D3DVIDEOSINK (bsink); sink_caps = gst_static_pad_template_get_caps (&sink_template); GST_DEBUG_OBJECT (sink, "In setcaps. Possible caps %" GST_PTR_FORMAT ", setting caps %" GST_PTR_FORMAT, sink_caps, caps); if (!gst_caps_can_intersect (sink_caps, caps)) goto incompatible_caps; if (!gst_video_format_parse_caps (caps, &sink->format, &video_width, &video_height)) goto invalid_format; if (!gst_video_parse_caps_framerate (caps, &fps_n, &fps_d) || !video_width || !video_height) goto incomplete_caps; /* get aspect ratio from caps if it's present, and * convert video width and height to a display width and height * using wd / hd = wv / hv * PARv / PARd */ /* get video's PAR */ if (!gst_video_parse_caps_pixel_aspect_ratio (caps, &video_par_n, &video_par_d)) { video_par_n = 1; video_par_d = 1; } /* get display's PAR */ if (sink->par) { display_par_n = gst_value_get_fraction_numerator (sink->par); display_par_d = gst_value_get_fraction_denominator (sink->par); } else { display_par_n = 1; display_par_d = 1; } if (!gst_video_calculate_display_ratio (&num, &den, video_width, video_height, video_par_n, video_par_d, display_par_n, display_par_d)) goto no_disp_ratio; GST_DEBUG_OBJECT (sink, "video width/height: %dx%d, calculated display ratio: %d/%d", video_width, video_height, num, den); /* now find a width x height that respects this display ratio. * prefer those that have one of w/h the same as the incoming video * using wd / hd = num / den */ /* start with same height, because of interlaced video */ /* check hd / den is an integer scale factor, and scale wd with the PAR */ if (video_height % den == 0) { GST_DEBUG_OBJECT (sink, "keeping video height"); GST_VIDEO_SINK_WIDTH (sink) = (guint) gst_util_uint64_scale_int (video_height, num, den); GST_VIDEO_SINK_HEIGHT (sink) = video_height; } else if (video_width % num == 0) { GST_DEBUG_OBJECT (sink, "keeping video width"); GST_VIDEO_SINK_WIDTH (sink) = video_width; GST_VIDEO_SINK_HEIGHT (sink) = (guint) gst_util_uint64_scale_int (video_width, den, num); } else { GST_DEBUG_OBJECT (sink, "approximating while keeping video height"); GST_VIDEO_SINK_WIDTH (sink) = (guint) gst_util_uint64_scale_int (video_height, num, den); GST_VIDEO_SINK_HEIGHT (sink) = video_height; } GST_DEBUG_OBJECT (sink, "scaling to %dx%d", GST_VIDEO_SINK_WIDTH (sink), GST_VIDEO_SINK_HEIGHT (sink)); if (GST_VIDEO_SINK_WIDTH (sink) <= 0 || GST_VIDEO_SINK_HEIGHT (sink) <= 0) goto no_display_size; sink->width = video_width; sink->height = video_height; /* Create a window (or start using an application-supplied one, then connect the graph */ gst_d3dvideosink_prepare_window (sink); return TRUE; /* ERRORS */ incompatible_caps: { GST_ERROR_OBJECT (sink, "caps incompatible"); return FALSE; } incomplete_caps: { GST_DEBUG_OBJECT (sink, "Failed to retrieve either width, " "height or framerate from intersected caps"); return FALSE; } invalid_format: { gchar *caps_txt = gst_caps_to_string (caps); GST_DEBUG_OBJECT (sink, "Could not locate image format from caps %s", caps_txt); g_free (caps_txt); return FALSE; } no_disp_ratio: { GST_ELEMENT_ERROR (sink, CORE, NEGOTIATION, (NULL), ("Error calculating the output display ratio of the video.")); return FALSE; } no_display_size: { GST_ELEMENT_ERROR (sink, CORE, NEGOTIATION, (NULL), ("Error calculating the output display ratio of the video.")); return FALSE; } } static gboolean gst_d3dvideosink_stop (GstBaseSink * bsink) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (bsink); gst_d3dvideosink_close_window (sink); return TRUE; } static void gst_d3dvideosink_flush_gpu (GstD3DVideoSink * sink) { LPDIRECT3DQUERY9 pEventQuery = NULL; IDirect3DDevice9_CreateQuery (sink->d3ddev, D3DQUERYTYPE_EVENT, &pEventQuery); if (pEventQuery) { IDirect3DQuery9_Issue (pEventQuery, D3DISSUE_END); /* Empty the command buffer and wait until the GPU is idle. */ while (S_FALSE == IDirect3DQuery9_GetData (pEventQuery, NULL, 0, D3DGETDATA_FLUSH)); IDirect3DQuery9_Release (pEventQuery); } } static void gst_d3dvideosink_wait_for_vsync (GstD3DVideoSink * sink) { if (sink->d3dpp.PresentationInterval == D3DPRESENT_INTERVAL_IMMEDIATE) { D3DRASTER_STATUS raster_stat; D3DDISPLAYMODE d3ddm; UINT lastScanline = 0; UINT vblankStart = 0; HANDLE thdl = GetCurrentThread (); int prio = GetThreadPriority (thdl); ZeroMemory (&d3ddm, sizeof (d3ddm)); IDirect3DDevice9_GetDisplayMode (sink->d3ddev, 0, &d3ddm); vblankStart = d3ddm.Height - 10; SetThreadPriority (thdl, THREAD_PRIORITY_TIME_CRITICAL); do { if (FAILED (IDirect3DDevice9_GetRasterStatus (sink->d3ddev, 0, &raster_stat))) { GST_ERROR_OBJECT (sink, "GetRasterStatus failed"); } break; if (!raster_stat.InVBlank) { if (raster_stat.ScanLine < lastScanline) { GST_INFO_OBJECT (sink, "missed last vsync curr : %d", raster_stat.ScanLine); break; } lastScanline = raster_stat.ScanLine; SwitchToThread (); } } while (raster_stat.ScanLine < vblankStart); SetThreadPriority (thdl, prio); } } static GstFlowReturn gst_d3dvideosink_show_frame (GstVideoSink * vsink, GstBuffer * buffer) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (vsink); LPDIRECT3DSURFACE9 drawSurface = NULL; GST_D3DVIDEOSINK_SWAP_CHAIN_LOCK (sink); if (!sink->d3ddev) { if (!shared.device_lost) { GST_ERROR_OBJECT (sink, "No Direct3D device has been created, stopping"); goto error; } else { GST_WARNING_OBJECT (sink, "Direct3D device is lost. Maintaining flow until it has been reset."); goto success; } } if (sink->window_closed) { GST_ERROR_OBJECT (sink, "Window has been closed, stopping"); goto error; } drawSurface = sink->d3d_offscreen_surface; if (SUCCEEDED (IDirect3DDevice9_BeginScene (sink->d3ddev))) { if (GST_BUFFER_DATA (buffer)) { D3DLOCKED_RECT lr; guint8 *dest, *source; int srcstride, dststride, i; IDirect3DSurface9_LockRect (drawSurface, &lr, NULL, 0); dest = (guint8 *) lr.pBits; source = GST_BUFFER_DATA (buffer); if (dest) { if (gst_video_format_is_yuv (sink->format)) { guint32 fourcc = gst_video_format_to_fourcc (sink->format); switch (fourcc) { case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'): case GST_MAKE_FOURCC ('Y', 'U', 'Y', 'V'): case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): dststride = lr.Pitch; srcstride = GST_BUFFER_SIZE (buffer) / sink->height; for (i = 0; i < sink->height; ++i) memcpy (dest + dststride * i, source + srcstride * i, srcstride); break; case GST_MAKE_FOURCC ('I', '4', '2', '0'): case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): { int srcystride, srcvstride, srcustride; int dstystride, dstvstride, dstustride; int rows; guint8 *srcv, *srcu, *dstv, *dstu; rows = sink->height; /* Source y, u and v strides */ srcystride = GST_ROUND_UP_4 (sink->width); srcustride = GST_ROUND_UP_8 (sink->width) / 2; srcvstride = GST_ROUND_UP_8 (srcystride) / 2; /* Destination y, u and v strides */ dstystride = lr.Pitch; dstustride = dstystride / 2; dstvstride = dstustride; srcu = source + srcystride * GST_ROUND_UP_2 (rows); srcv = srcu + srcustride * GST_ROUND_UP_2 (rows) / 2; if (fourcc == GST_MAKE_FOURCC ('I', '4', '2', '0')) { /* swap u and v planes */ dstv = dest + dstystride * rows; dstu = dstv + dstustride * rows / 2; } else { dstu = dest + dstystride * rows; dstv = dstu + dstustride * rows / 2; } for (i = 0; i < rows; ++i) { /* Copy the y plane */ memcpy (dest + dstystride * i, source + srcystride * i, srcystride); } for (i = 0; i < rows / 2; ++i) { /* Copy the u plane */ memcpy (dstu + dstustride * i, srcu + srcustride * i, srcustride); /* Copy the v plane */ memcpy (dstv + dstvstride * i, srcv + srcvstride * i, srcvstride); } break; } case GST_MAKE_FOURCC ('N', 'V', '1', '2'): { guint8 *dst = dest; int component; dststride = lr.Pitch; for (component = 0; component < 2; component++) { const int compHeight = gst_video_format_get_component_height (sink->format, component, sink->height); guint8 *src = source + gst_video_format_get_component_offset (sink->format, component, sink->width, sink->height); srcstride = gst_video_format_get_row_stride (sink->format, component, sink->width); for (i = 0; i < compHeight; i++) { memcpy (dst + dststride * i, src + srcstride * i, srcstride); } dst += dststride * compHeight; } break; } default: g_assert_not_reached (); } } else if (gst_video_format_is_rgb (sink->format)) { dststride = lr.Pitch; srcstride = GST_BUFFER_SIZE (buffer) / sink->height; for (i = 0; i < sink->height; ++i) memcpy (dest + dststride * i, source + srcstride * i, srcstride); } } IDirect3DSurface9_UnlockRect (drawSurface); } IDirect3DDevice9_EndScene (sink->d3ddev); } success: GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); gst_d3dvideosink_refresh (sink); return GST_FLOW_OK; #if 0 wrong_state: GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return GST_FLOW_WRONG_STATE; /*unexpected:*/ /* GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK(sink); */ /* */ /* return GST_FLOW_UNEXPECTED; */ #endif error: GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return GST_FLOW_ERROR; } /* Simply redraws the last item on our offscreen surface to the window */ static gboolean gst_d3dvideosink_refresh (GstD3DVideoSink * sink) { GST_D3DVIDEOSINK_SWAP_CHAIN_LOCK (sink); { HRESULT hr; LPDIRECT3DSURFACE9 backBuffer; if (!sink->d3ddev) { if (!shared.device_lost) GST_DEBUG ("No Direct3D device has been created"); goto error; } if (!sink->d3d_offscreen_surface) { GST_DEBUG ("No Direct3D offscreen surface has been created"); goto error; } if (sink->window_closed) { GST_DEBUG ("Window has been closed"); goto error; } /* Set the render target to our swap chain */ if (FAILED (IDirect3DDevice9_GetBackBuffer (sink->d3ddev, 0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer))) { GST_ERROR_OBJECT (sink, "failed to get back buffer"); goto error; } IDirect3DDevice9_SetRenderTarget (sink->d3ddev, 0, backBuffer); IDirect3DSurface9_Release (backBuffer); /* Clear the target */ IDirect3DDevice9_Clear (sink->d3ddev, 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB (0, 0, 0), 1.0f, 0); if (SUCCEEDED (IDirect3DDevice9_BeginScene (sink->d3ddev))) { gst_d3dvideosink_stretch (sink, backBuffer); IDirect3DDevice9_EndScene (sink->d3ddev); } IDirect3DSurface9_Release (backBuffer); gst_d3dvideosink_flush_gpu (sink); /* Swap back and front buffers on video card and present to the user */ if (FAILED (hr = IDirect3DDevice9_Present (sink->d3ddev, NULL, NULL, NULL, NULL))) { switch (hr) { case D3DERR_DEVICELOST: case D3DERR_DEVICENOTRESET: gst_d3dvideosink_notify_device_lost (sink); break; default: goto error; } } gst_d3dvideosink_flush_gpu (sink); } /*success:*/ GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return TRUE; error: GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return FALSE; } static gboolean gst_d3dvideosink_update_all (GstD3DVideoSink * sink) { GST_D3DVIDEOSINK_SHARED_D3D_LOCK { GList *item; GstD3DVideoSink *s; for (item = g_list_first (shared.element_list); item; item = item->next) { s = (GstD3DVideoSink *) item->data; gst_d3dvideosink_update (GST_BASE_SINK (s)); } } /*success:*/ GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK return TRUE; /*error:*/ /* GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK */ /* */ /* return FALSE; */ } static gboolean gst_d3dvideosink_refresh_all (GstD3DVideoSink * sink) { GST_D3DVIDEOSINK_SHARED_D3D_LOCK { GList *item; GstD3DVideoSink *s; for (item = g_list_first (shared.element_list); item; item = item->next) { s = (GstD3DVideoSink *) item->data; gst_d3dvideosink_refresh (s); } } /*success:*/ GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK return TRUE; /*error:*/ /* GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK */ /* return FALSE; */ } static void gst_d3dvideosink_stretch (GstD3DVideoSink * sink, LPDIRECT3DSURFACE9 backBuffer) { if (sink->keep_aspect_ratio) { gint window_width; gint window_height; RECT r; GstVideoRectangle src; GstVideoRectangle dst; GstVideoRectangle result; gdouble x_scale, y_scale; gst_d3dvideosink_window_size (sink, &window_width, &window_height); src.x = 0; src.y = 0; src.w = GST_VIDEO_SINK_WIDTH (sink); src.h = GST_VIDEO_SINK_HEIGHT (sink); dst.x = 0; dst.y = 0; dst.w = window_width; dst.h = window_height; x_scale = (gdouble) src.w / (gdouble) dst.w; y_scale = (gdouble) src.h / (gdouble) dst.h; gst_video_sink_center_rect (src, dst, &result, TRUE); result.x = result.x * x_scale; result.y = result.y * y_scale; result.w = result.w * x_scale; result.h = result.h * y_scale; //clip to src gst_video_sink_center_rect (result, src, &result, FALSE); r.left = result.x; r.top = result.y; r.right = result.x + result.w; r.bottom = result.y + result.h; if (FAILED (IDirect3DDevice9_StretchRect (sink->d3ddev, sink->d3d_offscreen_surface, NULL, backBuffer, &r, sink->d3dfiltertype))) { GST_ERROR_OBJECT (sink, "StretchRect failed"); } } else { IDirect3DDevice9_StretchRect (sink->d3ddev, sink->d3d_offscreen_surface, NULL, backBuffer, NULL, sink->d3dfiltertype); } } static void gst_d3dvideosink_expose (GstXOverlay * overlay) { GstBaseSink *sink = GST_BASE_SINK (overlay); gst_d3dvideosink_update (sink); } static void gst_d3dvideosink_update (GstBaseSink * bsink) { GstBuffer *last_buffer; last_buffer = gst_base_sink_get_last_buffer (bsink); if (last_buffer) { gst_d3dvideosink_show_frame (GST_VIDEO_SINK (bsink), last_buffer); gst_buffer_unref (last_buffer); } } /* TODO: How can we implement these? Figure that out... */ /* static gboolean gst_d3dvideosink_unlock (GstBaseSink * bsink) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (bsink); return TRUE; } static gboolean gst_d3dvideosink_unlock_stop (GstBaseSink * bsink) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (bsink); return TRUE; } */ static gboolean gst_d3dvideosink_initialize_direct3d (GstD3DVideoSink * sink) { DirectXAPI *api; GstD3DVideoSinkClass *klass; /* Let's hope this is never a problem (they have millions of d3d elements going at the same time) */ if (shared.element_count >= G_MAXINT32) { GST_ERROR ("There are too many d3dvideosink elements. Creating more elements would put this element into an unknown state."); return FALSE; } GST_D3DVIDEOSINK_SHARED_D3D_LOCK /* Add to our GList containing all of our elements. */ /* GLists are doubly-linked lists and calling prepend() prevents it from having to traverse the entire list just to add one item. */ shared.element_list = g_list_prepend (shared.element_list, sink); /* Increment our count of the number of elements we have */ shared.element_count++; if (shared.element_count > 1) goto success; /* We want to initialize direct3d only for the first element that's using it. */ /* We'll destroy this once all elements using direct3d have been finalized. */ /* See gst_d3dvideosink_release_direct3d() for details. */ if (!sink) { GST_WARNING ("Missing gobject instance."); return FALSE; } klass = GST_D3DVIDEOSINK_GET_CLASS (sink); if (!klass) { GST_WARNING ("Unable to retrieve gobject class"); goto error; } api = klass->directx_api; if (!api) { GST_WARNING ("Missing DirectX api"); goto error; } shared.d3d = (LPDIRECT3D9) DX9_D3D_COMPONENT_CALL_FUNC (DIRECTX_D3D (api), Direct3DCreate9, D3D_SDK_VERSION); if (!shared.d3d) { GST_ERROR ("Unable to create Direct3D interface"); goto error; } /* We create a window that's hidden, so we can control the device's from a single thread */ GST_DEBUG ("Creating hidden window for Direct3D"); if (!gst_d3dvideosink_create_shared_hidden_window (sink)) goto error; success: GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK return TRUE; error: GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK return FALSE; } static gboolean gst_d3dvideosink_initialize_d3d_device (GstD3DVideoSink * sink) { HRESULT hr; DWORD d3dcreate; D3DCAPS9 d3dcaps; HWND hwnd = sink->window_handle; D3DFORMAT d3dformat = sink->d3dformat; D3DFORMAT d3dfourcc; D3DDISPLAYMODE d3ddm; D3DTEXTUREFILTERTYPE d3dfiltertype; gint width, height; /* Get the current size of the window */ gst_d3dvideosink_window_size (sink, &width, &height); GST_D3DVIDEOSINK_SWAP_CHAIN_LOCK (sink); if (!shared.d3d) { GST_WARNING ("Direct3D object has not been initialized"); goto error; } if (FAILED (IDirect3D9_GetAdapterDisplayMode (shared.d3d, D3DADAPTER_DEFAULT, &d3ddm))) { GST_WARNING ("Unable to request adapter display mode"); goto error; } if (FAILED (IDirect3D9_GetDeviceCaps (shared.d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dcaps))) { GST_WARNING ("Unable to request device caps"); goto error; } /* Ask DirectX to please not clobber the FPU state when making DirectX API calls. */ /* This can cause libraries such as cairo to misbehave in certain scenarios. */ d3dcreate = 0 | D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED; /* Determine vertex processing capabilities. Some cards have issues using software vertex processing. */ /* Courtesy http://www.chadvernon.com/blog/resources/directx9/improved-direct3d-initialization/ */ if ((d3dcaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == D3DDEVCAPS_HWTRANSFORMANDLIGHT) { d3dcreate |= D3DCREATE_HARDWARE_VERTEXPROCESSING; /* if ((d3dcaps.DevCaps & D3DDEVCAPS_PUREDEVICE) == D3DDEVCAPS_PUREDEVICE) */ /* d3dcreate |= D3DCREATE_PUREDEVICE; */ } else { d3dcreate |= D3DCREATE_SOFTWARE_VERTEXPROCESSING; } /* Check the filter type. */ if ((d3dcaps.StretchRectFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) == D3DPTFILTERCAPS_MINFLINEAR && (d3dcaps.StretchRectFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR) == D3DPTFILTERCAPS_MAGFLINEAR) { d3dfiltertype = D3DTEXF_LINEAR; } else { d3dfiltertype = D3DTEXF_NONE; } if (gst_video_format_is_yuv (sink->format)) { switch (gst_video_format_to_fourcc (sink->format)) { case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'): d3dformat = D3DFMT_X8R8G8B8; d3dfourcc = (D3DFORMAT) MAKEFOURCC ('Y', 'U', 'Y', '2'); break; //case GST_MAKE_FOURCC ('Y', 'U', 'V', 'Y'): // d3dformat = D3DFMT_X8R8G8B8; // d3dfourcc = (D3DFORMAT)MAKEFOURCC('Y', 'U', 'V', 'Y'); // break; case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): d3dformat = D3DFMT_X8R8G8B8; d3dfourcc = (D3DFORMAT) MAKEFOURCC ('U', 'Y', 'V', 'Y'); break; case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): case GST_MAKE_FOURCC ('I', '4', '2', '0'): d3dformat = D3DFMT_X8R8G8B8; d3dfourcc = (D3DFORMAT) MAKEFOURCC ('Y', 'V', '1', '2'); break; case GST_MAKE_FOURCC ('N', 'V', '1', '2'): d3dformat = D3DFMT_X8R8G8B8; d3dfourcc = (D3DFORMAT) MAKEFOURCC ('N', 'V', '1', '2'); break; default: g_assert_not_reached (); goto error; } } else if (gst_video_format_is_rgb (sink->format)) { d3dformat = D3DFMT_X8R8G8B8; d3dfourcc = D3DFMT_X8R8G8B8; } else { g_assert_not_reached (); goto error; } GST_DEBUG ("Determined Direct3D format: %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (d3dfourcc)); //Stencil/depth buffers aren't created by default when using swap chains //if (SUCCEEDED(IDirect3D9_CheckDeviceFormat(shared.d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dformat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D32))) { // d3dstencilformat = D3DFMT_D32; // d3dEnableAutoDepthStencil = TRUE; //} else if (SUCCEEDED(IDirect3D9_CheckDeviceFormat(shared.d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dformat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D24X8))) { // d3dstencilformat = D3DFMT_D24X8; // d3dEnableAutoDepthStencil = TRUE; //} else if (SUCCEEDED(IDirect3D9_CheckDeviceFormat(shared.d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dformat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D16))) { // d3dstencilformat = D3DFMT_D16; // d3dEnableAutoDepthStencil = TRUE; //} else { // d3dstencilformat = D3DFMT_X8R8G8B8; // d3dEnableAutoDepthStencil = FALSE; //} // //GST_DEBUG("Determined Direct3D stencil format: %d", d3dstencilformat); GST_DEBUG ("Direct3D back buffer size: %dx%d", GST_VIDEO_SINK_WIDTH (sink), GST_VIDEO_SINK_HEIGHT (sink)); sink->d3dformat = d3dformat; sink->d3dfourcc = d3dfourcc; ZeroMemory (&sink->d3dpp, sizeof (sink->d3dpp)); sink->d3dpp.Flags = D3DPRESENTFLAG_VIDEO | D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; sink->d3dpp.Windowed = TRUE; sink->d3dpp.hDeviceWindow = hwnd; sink->d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; sink->d3dpp.BackBufferCount = 1; //sink->d3dpp.BackBufferFormat = d3dformat; sink->d3dpp.BackBufferWidth = sink->width; sink->d3dpp.BackBufferHeight = sink->height; sink->d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; sink->d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; GST_DEBUG ("Creating Direct3D device for window %p", hwnd); sink->d3ddev = NULL; if (FAILED (hr = IDirect3D9_CreateDevice (shared.d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, d3dcreate, &sink->d3dpp, &sink->d3ddev))) { GST_WARNING ("Unable to create Direct3D device. Result: %ld (0x%lx)", hr, hr); goto error; } if (FAILED (IDirect3DDevice9_CreateOffscreenPlainSurface (sink->d3ddev, sink->width, sink->height, d3dfourcc, D3DPOOL_DEFAULT, &sink->d3d_offscreen_surface, NULL))) { goto error; } /* Determine texture filtering support. If it's supported for this format, use the filter type determined when we created the dev and checked the dev caps. */ if (FAILED (IDirect3D9_CheckDeviceFormat (shared.d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dformat, D3DUSAGE_QUERY_FILTER, D3DRTYPE_TEXTURE, d3dformat))) { d3dfiltertype = D3DTEXF_NONE; } GST_DEBUG ("Direct3D stretch rect texture filter: %d", d3dfiltertype); sink->d3dfiltertype = d3dfiltertype; //if (FAILED(IDirect3DDevice9_GetDeviceCaps( // d3ddev, // &d3dcaps //))) { // /* Prevent memory leak */ // IDirect3D9_Release(d3d); // GST_WARNING ("Unable to retrieve Direct3D device caps"); // goto error; //} /*success:*/ GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return TRUE; error: GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return FALSE; } static gboolean gst_d3dvideosink_resize_swap_chain (GstD3DVideoSink * sink, gint width, gint height) { if (width <= 0 || height <= 0 || width > GetSystemMetrics (SM_CXFULLSCREEN) || height > GetSystemMetrics (SM_CYFULLSCREEN)) { GST_DEBUG ("Invalid size"); return FALSE; } if (!shared.d3d || !sink->d3ddev) { GST_DEBUG_OBJECT (sink, "Direct3D device not initialized."); return TRUE; } GST_LOG_OBJECT (sink, "Resizing Direct3D swap chain for sink %p to %dx%d", sink, width, height); /* actually recreates the device + associated surfaces must be called from thread that created the device */ if (!gst_d3dvideosink_notify_device_resize (sink)) goto error; /* success:*/ GST_LOG_OBJECT (sink, "Direct3D swap chain successfully resized"); return TRUE; error: GST_ERROR_OBJECT (sink, "Error attempting to resize the Direct3D swap chain"); return FALSE; } static gboolean gst_d3dvideosink_notify_device_init (GstD3DVideoSink * sink) { if (sink->window_handle) { SendMessage (shared.hidden_window_handle, WM_DIRECTX_D3D_INIT_DEVICE, 0, (LPARAM) sink); } return TRUE; } static gboolean gst_d3dvideosink_notify_device_resize (GstD3DVideoSink * sink) { if (sink->window_handle) { SendMessage (shared.hidden_window_handle, WM_DIRECTX_D3D_RESIZE, 0, (LPARAM) sink); } return TRUE; } static gboolean gst_d3dvideosink_notify_device_lost (GstD3DVideoSink * sink) { /* Send notification asynchronously */ PostMessage (shared.hidden_window_handle, WM_DIRECTX_D3D_INIT_DEVICELOST, 0, (LPARAM) sink); /*success:*/ GST_DEBUG ("Successfully sent notification of device lost event for sink %p", sink); return TRUE; /*error:*/ /* GST_DEBUG("Error attempting to send notification of device lost event for sink %d", sink); */ /* GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK */ /* */ /* return FALSE; */ } static gboolean gst_d3dvideosink_notify_device_reset (GstD3DVideoSink * sink) { { /* Send notification synchronously -- let's ensure the timer's been killed before returning */ SendMessage (shared.hidden_window_handle, WM_DIRECTX_D3D_END_DEVICELOST, 0, (LPARAM) sink); } /*success:*/ GST_DEBUG ("Successfully sent notification of device reset event for sink %p", sink); return TRUE; /*error:*/ /* GST_DEBUG("Error attempting to send notification of reset lost event for sink %d", sink); */ /* GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK */ /* */ /* return FALSE; */ } static gboolean gst_d3dvideosink_device_lost (GstD3DVideoSink * sink) { /* Must be called from hidden window's message loop! */ GST_D3DVIDEOSINK_SWAP_CHAIN_LOCK (sink); { if (shared.device_lost) GST_DEBUG ("Direct3D device lost"); GST_DEBUG_OBJECT (sink, ". Resetting the device."); if (g_thread_self () != shared.hidden_window_thread) { GST_ERROR ("Direct3D device can only be reset by the thread that created it."); goto error; } if (!shared.d3d) { GST_ERROR ("Direct3D device has not been initialized"); goto error; } { //GList *item; //GstD3DVideoSink *s; /* This is technically a bit different from the normal. We don't call reset(), instead */ /* we recreate everything from scratch. */ /* Release the device */ if (!gst_d3dvideosink_release_d3d_device (sink)) goto error; /* Recreate device */ if (!gst_d3dvideosink_initialize_d3d_device (sink)) goto error; /* Reinitialize all swap chains, surfaces, buffers, etc. */ /* for (item = g_list_first (shared.element_list); item; item = item->next) { s = (GstD3DVideoSink *) item->data; gst_d3dvideosink_initialize_swap_chain (s); } */ } /* Let the hidden window know that it's okay to kill the timer */ gst_d3dvideosink_notify_device_reset (sink); } /*success:*/ GST_DEBUG ("Direct3D device has successfully been reset."); GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return TRUE; error: GST_DEBUG ("Unable to successfully reset the Direct3D device."); GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return FALSE; } static gboolean gst_d3dvideosink_release_d3d_device (GstD3DVideoSink * sink) { GST_D3DVIDEOSINK_SWAP_CHAIN_LOCK (sink); if (sink->d3ddev) { int ref_count; ref_count = IDirect3DDevice9_Release (sink->d3ddev); sink->d3ddev = NULL; GST_DEBUG_OBJECT (sink, "Direct3D device released. Reference count: %d", ref_count); } if (sink->d3d_offscreen_surface) { int ref_count; ref_count = IDirect3DSurface9_Release (sink->d3d_offscreen_surface); sink->d3d_offscreen_surface = NULL; GST_DEBUG_OBJECT (sink, "Direct3D offscreen surface released. Reference count: %d", ref_count); } GST_D3DVIDEOSINK_SWAP_CHAIN_UNLOCK (sink); return TRUE; } static gboolean gst_d3dvideosink_release_direct3d (GstD3DVideoSink * sink) { GST_DEBUG ("Cleaning all Direct3D objects"); GST_D3DVIDEOSINK_SHARED_D3D_LOCK /* Be absolutely sure that we've released this sink's hook (if any). */ gst_d3dvideosink_unhook_window_for_renderer (sink); /* Remove item from the list */ shared.element_list = g_list_remove (shared.element_list, sink); /* Decrement our count of the number of elements we have */ shared.element_count--; if (shared.element_count < 0) shared.element_count = 0; if (shared.element_count > 0) goto success; gst_d3dvideosink_release_d3d_device (sink); if (shared.d3d) { int ref_count; ref_count = IDirect3D9_Release (shared.d3d); shared.d3d = NULL; GST_DEBUG ("Direct3D object released. Reference count: %d", ref_count); } GST_DEBUG ("Closing hidden Direct3D window"); gst_d3dvideosink_close_shared_hidden_window (sink); success: GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK return TRUE; /*error:*/ /* GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK */ /* return FALSE; */ } static gboolean gst_d3dvideosink_window_size (GstD3DVideoSink * sink, gint * width, gint * height) { if (!sink || !sink->window_handle) { if (width && height) { *width = 0; *height = 0; } return FALSE; } { RECT sz; GetClientRect (sink->window_handle, &sz); *width = MAX (1, ABS (sz.right - sz.left)); *height = MAX (1, ABS (sz.bottom - sz.top)); } return TRUE; } static void gst_d3dvideosink_navigation_send_event (GstNavigation * navigation, GstStructure * structure) { GstD3DVideoSink *sink = GST_D3DVIDEOSINK (navigation); gint window_width; gint window_height; GstEvent *e; GstVideoRectangle src, dst, result; double x, y, old_x, old_y; GstPad *pad = NULL; gst_d3dvideosink_window_size (sink, &window_width, &window_height); src.w = GST_VIDEO_SINK_WIDTH (sink); src.h = GST_VIDEO_SINK_HEIGHT (sink); dst.w = window_width; dst.h = window_height; e = gst_event_new_navigation (structure); if (sink->keep_aspect_ratio) { gst_video_sink_center_rect (src, dst, &result, TRUE); } else { result.x = 0; result.y = 0; result.w = dst.w; result.h = dst.h; } /* 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 = 0; } else if (x >= result.x + result.w) { x = src.w; } else { x = MAX (0, MIN (src.w, MAX (0, x - result.x) / result.w * src.w)); } GST_DEBUG_OBJECT (sink, "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 = 0; } else if (y >= result.y + result.h) { y = src.h; } else { y = MAX (0, MIN (src.h, MAX (0, y - result.y) / result.h * src.h)); } GST_DEBUG_OBJECT (sink, "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 (sink)); if (GST_IS_PAD (pad) && GST_IS_EVENT (e)) { gst_pad_send_event (pad, e); gst_object_unref (pad); } } static gboolean gst_d3dvideosink_direct3d_supported (GstD3DVideoSink * sink) { GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink); return (klass != NULL && klass->is_directx_supported); } static void gst_d3dvideosink_log_debug (const gchar * file, const gchar * function, gint line, const gchar * format, va_list args) { if (G_UNLIKELY (GST_LEVEL_DEBUG <= __gst_debug_min)) gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, file, function, line, NULL, format, args); } static void gst_d3dvideosink_log_warning (const gchar * file, const gchar * function, gint line, const gchar * format, va_list args) { if (G_UNLIKELY (GST_LEVEL_WARNING <= __gst_debug_min)) gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_WARNING, file, function, line, NULL, format, args); } static void gst_d3dvideosink_log_error (const gchar * file, const gchar * function, gint line, const gchar * format, va_list args) { if (G_UNLIKELY (GST_LEVEL_ERROR <= __gst_debug_min)) gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_ERROR, file, function, line, NULL, format, args); } /* Plugin entry point */ static gboolean plugin_init (GstPlugin * plugin) { /* PRIMARY: this is the best videosink to use on windows */ if (!gst_element_register (plugin, "d3dvideosink", GST_RANK_PRIMARY, GST_TYPE_D3DVIDEOSINK)) return FALSE; return TRUE; } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "d3dsinkwrapper", "Direct3D sink wrapper plugin", plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)