gstreamer/sys/d3dvideosink/d3dvideosink.c
2012-06-29 12:03:51 +01:00

2446 lines
74 KiB
C

/* GStreamer
* Copyright (C) 2010-2011 David Hoyt <dhoyt@hoytsoft.org>
* Copyright (C) 2010 Andoni Morales <ylatuya@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#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_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 ("video/x-raw, "
"format = (string) { I420, YV12, YUY2, UYVY, BGRx, BGRA, NV12 }, "
"framerate = (fraction) [ 0, MAX ], "
"width = (int) [ 1, MAX ], "
"height = (int) [ 1, MAX ]")
);
static void gst_d3dvideosink_navigation_init (GstNavigationInterface * iface);
static void gst_d3dvideosink_video_overlay_init (GstVideoOverlayInterface *
iface);
#define gst_d3dvideosink_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstD3DVideoSink, gst_d3dvideosink, GST_TYPE_VIDEO_SINK,
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
gst_d3dvideosink_navigation_init);
G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
gst_d3dvideosink_video_overlay_init));
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, GstCaps * filter);
static GstFlowReturn gst_d3dvideosink_show_frame (GstVideoSink * sink,
GstBuffer * buffer);
/* GstVideoOverlay methods */
static void gst_d3dvideosink_set_window_handle (GstVideoOverlay * overlay,
guintptr window_id);
static void gst_d3dvideosink_expose (GstVideoOverlay * 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_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_reinit (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 void
gst_d3dvideosink_video_overlay_init (GstVideoOverlayInterface * iface)
{
iface->set_window_handle = gst_d3dvideosink_set_window_handle;
iface->expose = gst_d3dvideosink_expose;
}
static void
gst_d3dvideosink_navigation_init (GstNavigationInterface * iface)
{
iface->send_event = gst_d3dvideosink_navigation_send_event;
}
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", TRUE,
(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));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&sink_template));
gst_element_class_set_static_metadata (gstelement_class, "Direct3D video sink",
"Sink/Video",
"Display data using a Direct3D video renderer",
"David Hoyt <dhoyt@hoytsoft.org>");
/* 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)
{
gst_d3dvideosink_clear (sink);
sink->d3d_device_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_device_lock);
sink->d3d_device_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_device_caps (GstBaseSink *basesink, D3DDISPLAYMODE d3ddm)
{
GstD3DVideoSink *sink = GST_D3DVIDEOSINK (basesink);
gint i;
GstCaps *caps;
GstCaps *c;
caps = gst_caps_new_empty ();
c = gst_caps_normalize (gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (sink)));
for (i = 0; i < gst_caps_get_size (c); i++) {
GstStructure *stru;
stru = gst_caps_get_structure (c, i);
if (gst_structure_has_name (stru, "video/x-raw"))
{
GstVideoFormat format;
const gchar *s;
D3DFORMAT d3dfourcc = 0;
s = gst_structure_get_string (stru, "format");
format = gst_video_format_from_string (s);
switch (format)
{
case GST_VIDEO_FORMAT_YV12:
case GST_VIDEO_FORMAT_I420:
d3dfourcc = (D3DFORMAT) MAKEFOURCC ('Y', 'V', '1', '2');
break;
case GST_VIDEO_FORMAT_RGB:
d3dfourcc = D3DFMT_R8G8B8;
break;
case GST_VIDEO_FORMAT_ARGB:
d3dfourcc = D3DFMT_A8R8G8B8;
break;
case GST_VIDEO_FORMAT_xRGB:
d3dfourcc = D3DFMT_X8R8G8B8;
break;
case GST_VIDEO_FORMAT_RGB16:
d3dfourcc = D3DFMT_R5G6B5;
break;
case GST_VIDEO_FORMAT_ABGR:
d3dfourcc = D3DFMT_A8B8G8R8;
break;
case GST_VIDEO_FORMAT_xBGR:
d3dfourcc = D3DFMT_X8B8G8R8;
break;
case GST_VIDEO_FORMAT_GRAY8:
d3dfourcc = D3DFMT_L8;
break;
case GST_VIDEO_FORMAT_UYVY:
d3dfourcc = D3DFMT_UYVY;
break;
case GST_VIDEO_FORMAT_YUY2:
d3dfourcc = D3DFMT_YUY2;
break;
default:
break;
}
if (d3dfourcc == 0)
continue;
if (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));
}
}
}
gst_caps_unref (c);
return caps;
}
static GstCaps *
gst_d3dvideosink_get_caps (GstBaseSink * basesink, GstCaps * filter)
{
GstD3DVideoSink *sink = GST_D3DVIDEOSINK (basesink);
GstCaps *caps;
/* 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");
caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (sink));
} else {
caps = gst_d3dvideosink_get_device_caps (basesink, d3ddm);
}
} else {
caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (sink));
if (filter)
{
GstCaps *intersection;
intersection =
gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = intersection;
}
}
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);
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;
shared.device_lost = FALSE;
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");
if (!gst_d3dvideosink_initialize_d3d_device (sink))
gst_d3dvideosink_notify_device_lost(sink);
else
GST_DEBUG ("Direct3D initialization complete");
break;
}
case WM_DIRECTX_D3D_INIT_DEVICELOST:
{
if (shared.device_lost)
break;
shared.device_lost = TRUE;
GST_D3DVIDEOSINK_D3D_DEVICE_LOCK (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);
shared.device_lost_sink = sink;
/* 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)
break;
/* 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;
GST_D3DVIDEOSINK_D3D_DEVICE_UNLOCK (sink);
/* 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"));
if (!sink)
return FALSE;
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_video_overlay_got_window_handle (GST_VIDEO_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)
{
if (!sink)
return;
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 (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;
}
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 (GstVideoOverlay * 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;
}
GST_D3DVIDEOSINK_D3D_DEVICE_LOCK (sink);
/* 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);
sink->window_closed = FALSE;
gst_d3dvideosink_notify_device_reinit (sink);
} else {
sink->window_handle = hWnd;
}
GST_D3DVIDEOSINK_D3D_DEVICE_UNLOCK (sink);
GST_DEBUG_OBJECT (sink, "Direct3D window id successfully changed to %p",
hWnd);
gst_d3dvideosink_update (GST_BASE_SINK_CAST (sink));
return;
}
/* Hook for out-of-process rendering */
LRESULT CALLBACK
gst_d3dvideosink_hook_proc (int nCode, WPARAM wParam, LPARAM 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_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"));
}
}
static void
gst_d3dvideosink_prepare_window (GstD3DVideoSink * sink)
{
/* Give the app a last chance to supply a window id */
if (!sink->window_handle) {
gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (sink));
}
/* If the app supplied one, use it. Otherwise, go ahead
* and create (and use) our own window, if we didn't create
* one before */
if (sink->window_handle && sink->is_new_window) {
GST_D3DVIDEOSINK_D3D_DEVICE_LOCK(sink);
gst_d3dvideosink_release_d3d_device(sink);
GST_D3DVIDEOSINK_D3D_DEVICE_UNLOCK(sink);
} else if (sink->window_handle) {
gst_d3dvideosink_set_window_for_renderer (sink);
} else {
gst_d3dvideosink_create_default_window (sink);
}
GST_D3DVIDEOSINK_D3D_DEVICE_LOCK(sink);
gst_d3dvideosink_notify_device_init (sink);
GST_D3DVIDEOSINK_D3D_DEVICE_UNLOCK(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:
if (!sink->is_new_window) {
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;
/*GstStructure *structure;*/
GstCaps *sink_caps;
GstVideoInfo info;
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 */
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_info_from_caps (&info, caps))
goto invalid_format;
/*structure = gst_caps_get_structure (caps, 0);*/
video_width = info.width;
video_height = info.height;
/* 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 */
video_par_n = info.par_n;
video_par_d = info.par_d;
/* 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, info.width,
info.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 (info.height % den == 0) {
GST_DEBUG_OBJECT (sink, "keeping video height");
GST_VIDEO_SINK_WIDTH (sink) = (guint)
gst_util_uint64_scale_int (info.height, num, den);
GST_VIDEO_SINK_HEIGHT (sink) = info.height;
} else if (info.width % num == 0) {
GST_DEBUG_OBJECT (sink, "keeping video width");
GST_VIDEO_SINK_WIDTH (sink) = info.width;
GST_VIDEO_SINK_HEIGHT (sink) = (guint)
gst_util_uint64_scale_int (info.width, den, num);
} else {
GST_DEBUG_OBJECT (sink, "approximating while keeping video height");
GST_VIDEO_SINK_WIDTH (sink) = (guint)
gst_util_uint64_scale_int (info.height, num, den);
GST_VIDEO_SINK_HEIGHT (sink) = info.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->info = info;
sink->format = GST_VIDEO_INFO_FORMAT (&info);
/* 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;
}
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 G_GNUC_UNUSED 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;
if (!GST_D3DVIDEOSINK_D3D_DEVICE_TRYLOCK (sink))
return GST_FLOW_OK;
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))) {
GstMapInfo map;
if (gst_buffer_map (buffer, &map, GST_MAP_READ)) {
D3DLOCKED_RECT lr;
guint8 *dest, *source;
int srcstride, dststride, i;
IDirect3DSurface9_LockRect (drawSurface, &lr, NULL, 0);
dest = (guint8 *) lr.pBits;
source = map.data;
if (dest) {
if (GST_VIDEO_INFO_IS_YUV (&sink->info)) {
switch (sink->format) {
case GST_VIDEO_FORMAT_YUY2:
case GST_VIDEO_FORMAT_UYVY:
dststride = lr.Pitch;
srcstride = gst_buffer_get_size (buffer) / GST_VIDEO_SINK_HEIGHT (sink);
for (i = 0; i < GST_VIDEO_SINK_HEIGHT (sink); ++i)
memcpy (dest + dststride * i, source + srcstride * i,
srcstride);
break;
case GST_VIDEO_FORMAT_I420:
case GST_VIDEO_FORMAT_YV12:
{
int srcystride, srcvstride, srcustride;
int dstystride, dstvstride, dstustride;
int rows;
guint8 *srcv, *srcu, *dstv, *dstu;
rows = GST_VIDEO_SINK_HEIGHT (sink);
/* Source y, u and v strides */
srcystride = GST_ROUND_UP_4 (GST_VIDEO_SINK_WIDTH (sink));
srcustride = GST_ROUND_UP_8 (GST_VIDEO_SINK_WIDTH (sink)) / 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 (sink->format == GST_VIDEO_FORMAT_I420) {
/* 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_VIDEO_FORMAT_NV12:
{
guint8 *dst = dest;
int component;
dststride = lr.Pitch;
for (component = 0; component < 2; component++) {
const int compHeight = GST_VIDEO_INFO_COMP_HEIGHT (&sink->info, component);
guint8 *src = source + GST_VIDEO_INFO_COMP_OFFSET(&sink->info, component);
srcstride = GST_VIDEO_INFO_COMP_STRIDE(&sink->info, component);
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_INFO_IS_RGB (&sink->info)) {
dststride = lr.Pitch;
srcstride = gst_buffer_get_size (buffer) / GST_VIDEO_SINK_HEIGHT (sink);
for (i = 0; i < GST_VIDEO_SINK_HEIGHT (sink); ++i)
memcpy (dest + dststride * i, source + srcstride * i, srcstride);
}
}
IDirect3DSurface9_UnlockRect (drawSurface);
gst_buffer_unmap (buffer, &map);
}
IDirect3DDevice9_EndScene (sink->d3ddev);
}
success:
GST_D3DVIDEOSINK_D3D_DEVICE_UNLOCK (sink);
gst_d3dvideosink_refresh (sink);
return GST_FLOW_OK;
error:
GST_D3DVIDEOSINK_D3D_DEVICE_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)
{
HRESULT hr;
LPDIRECT3DSURFACE9 backBuffer;
if (!GST_D3DVIDEOSINK_D3D_DEVICE_TRYLOCK (sink))
return TRUE;
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_D3D_DEVICE_UNLOCK (sink);
return TRUE;
error:
GST_D3DVIDEOSINK_D3D_DEVICE_UNLOCK (sink);
return FALSE;
}
static gboolean
gst_d3dvideosink_update_all (GstD3DVideoSink * sink)
{
GList *item;
GstD3DVideoSink *s;
GST_D3DVIDEOSINK_SHARED_D3D_LOCK;
for (item = g_list_first (shared.element_list); item; item = item->next) {
s = (GstD3DVideoSink *) item->data;
gst_d3dvideosink_update (GST_BASE_SINK (s));
}
GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK;
return TRUE;
}
static gboolean
gst_d3dvideosink_refresh_all (GstD3DVideoSink * sink)
{
GList *item;
GstD3DVideoSink *s;
GST_D3DVIDEOSINK_SHARED_D3D_LOCK;
for (item = g_list_first (shared.element_list); item; item = item->next) {
s = (GstD3DVideoSink *) item->data;
gst_d3dvideosink_refresh (s);
}
GST_D3DVIDEOSINK_SHARED_D3D_UNLOCK;
return TRUE;
}
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 (GstVideoOverlay * overlay)
{
GstBaseSink *sink = GST_BASE_SINK (overlay);
gst_d3dvideosink_update (sink);
}
static void
gst_d3dvideosink_update (GstBaseSink * bsink)
{
GstSample *last_sample;
GstBuffer *last_buffer;
last_sample = gst_base_sink_get_last_sample (bsink);
if (last_sample) {
last_buffer = gst_sample_get_buffer (last_sample);
if (last_buffer)
gst_d3dvideosink_show_frame (GST_VIDEO_SINK (bsink), last_buffer);
gst_sample_unref (last_sample);
}
}
/* 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);
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_INFO_IS_YUV (&sink->info)) {
switch (sink->format) {
case GST_VIDEO_FORMAT_YUY2:
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_VIDEO_FORMAT_UYVY:
d3dformat = D3DFMT_X8R8G8B8;
d3dfourcc = (D3DFORMAT) MAKEFOURCC ('U', 'Y', 'V', 'Y');
break;
case GST_VIDEO_FORMAT_YV12:
case GST_VIDEO_FORMAT_I420:
d3dformat = D3DFMT_X8R8G8B8;
d3dfourcc = (D3DFORMAT) MAKEFOURCC ('Y', 'V', '1', '2');
break;
case GST_VIDEO_FORMAT_NV12:
d3dformat = D3DFMT_X8R8G8B8;
d3dfourcc = (D3DFORMAT) MAKEFOURCC ('N', 'V', '1', '2');
break;
default:
g_assert_not_reached ();
goto error;
}
} else if (GST_VIDEO_INFO_IS_RGB (&sink->info)) {
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));
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 = GST_VIDEO_SINK_WIDTH (sink);
sink->d3dpp.BackBufferHeight = GST_VIDEO_SINK_HEIGHT (sink);
sink->d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
sink->d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
GST_DEBUG ("Creating Direct3D device for window %p", hwnd);
sink->d3ddev = NULL;
hr = IDirect3D9_CreateDevice (shared.d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, d3dcreate, &sink->d3dpp, &sink->d3ddev);
if (FAILED (hr))
{
GST_WARNING ("Unable to create Direct3D device. Result: %ld (0x%lx)", hr, hr);
goto error;
}
hr = IDirect3DDevice9_CreateOffscreenPlainSurface (sink->d3ddev, GST_VIDEO_SINK_WIDTH (sink), GST_VIDEO_SINK_HEIGHT (sink), d3dfourcc, D3DPOOL_DEFAULT, &sink->d3d_offscreen_surface, NULL);
if (FAILED (hr)) {
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;
/*success:*/
return TRUE;
error:
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_reinit (GstD3DVideoSink * sink)
{
if (sink->window_handle) {
SendMessage (shared.hidden_window_handle, WM_DIRECTX_D3D_DEVICELOST, 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);
GST_DEBUG ("Successfully sent notification of device lost event for sink %p",
sink);
return TRUE;
}
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);
}
GST_DEBUG ("Successfully sent notification of device reset event for sink %p",
sink);
return TRUE;
}
static gboolean
gst_d3dvideosink_device_lost (GstD3DVideoSink * sink)
{
/* Must be called from hidden window's message loop! */
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;
}
/* 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;
/* Let the hidden window know that it's okay to kill the timer */
gst_d3dvideosink_notify_device_reset (sink);
GST_DEBUG ("Direct3D device has successfully been reset.");
return TRUE;
error:
GST_DEBUG ("Unable to successfully reset the Direct3D device.");
return FALSE;
}
static gboolean
gst_d3dvideosink_release_d3d_device (GstD3DVideoSink * sink)
{
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);
}
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);
}
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_D3D_DEVICE_LOCK (sink);
gst_d3dvideosink_release_d3d_device (sink);
GST_D3DVIDEOSINK_D3D_DEVICE_UNLOCK (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;
}
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;
GST_DEBUG_CATEGORY_INIT (d3dvideosink_debug, "d3dvideosink", 0,
"Direct3D video sink");
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)