gstreamer/sys/d3dvideosink/d3dhelpers.c
Sebastian Dröge d8b1efe13a d3dvideosink: Don't try to recreate swapchain while the window is minimized
It will fail and cause the sink to crash. Instead wait until the window is
visible again before checking if the swapchain really has to be recreated.

https://bugzilla.gnome.org/show_bug.cgi?id=741608
2015-11-30 19:53:28 +02:00

2703 lines
73 KiB
C

/* GStreamer
* Copyright (C) 2012 Roland Krikava <info@bluedigits.com>
* Copyright (C) 2010-2011 David Hoyt <dhoyt@hoytsoft.org>
* Copyright (C) 2010 Andoni Morales <ylatuya@gmail.com>
* Copyright (C) 2012 Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* 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"
#include "d3dhelpers.h"
#include <stdio.h>
typedef enum
{
WINDOW_VISIBILITY_FULL = 1,
WINDOW_VISIBILITY_PARTIAL = 2,
WINDOW_VISIBILITY_HIDDEN = 3,
WINDOW_VISIBILITY_ERROR = 4
} WindowHandleVisibility;
/** FWD DECLS **/
static gboolean d3d_hidden_window_thread (GstD3DVideoSinkClass * klass);
static gboolean d3d_window_wndproc_set (GstD3DVideoSink * sink);
static void d3d_window_wndproc_unset (GstD3DVideoSink * sink);
static gboolean d3d_init_swap_chain (GstD3DVideoSink * sink, HWND hWnd);
static gboolean d3d_release_swap_chain (GstD3DVideoSink * sink);
static gboolean d3d_resize_swap_chain (GstD3DVideoSink * sink);
static gboolean d3d_present_swap_chain (GstD3DVideoSink * sink);
static gboolean d3d_copy_buffer (GstD3DVideoSink * sink,
GstBuffer * from, GstBuffer * to);
static gboolean d3d_stretch_and_copy (GstD3DVideoSink * sink,
LPDIRECT3DSURFACE9 back_buffer);
static HWND d3d_create_internal_window (GstD3DVideoSink * sink);
static void d3d_class_notify_device_lost (GstD3DVideoSink * sink);
static LRESULT APIENTRY d3d_wnd_proc_internal (HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam);
static LRESULT APIENTRY d3d_wnd_proc (HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam);
GST_DEBUG_CATEGORY_EXTERN (gst_d3dvideosink_debug);
#define GST_CAT_DEFAULT gst_d3dvideosink_debug
static gint WM_D3DVIDEO_NOTIFY_DEVICE_LOST = 0;
#define IDT_DEVICE_RESET_TIMER 0
#define WM_QUIT_THREAD WM_USER+0
/** Helpers **/
#define ERROR_CHECK_HR(hr) \
if(hr != S_OK) { \
const gchar * str_err=NULL, *t1=NULL; \
gchar tmp[128]=""; \
switch(hr)
#define CASE_HR_ERR(hr_err) \
case hr_err: str_err = #hr_err; break;
#define CASE_HR_DBG_ERR_END(sink, gst_err_msg, level) \
default: \
t1=gst_err_msg; \
sprintf(tmp, "HR-SEV:%u HR-FAC:%u HR-CODE:%u", (guint)HRESULT_SEVERITY(hr), (guint)HRESULT_FACILITY(hr), (guint)HRESULT_CODE(hr)); \
str_err = tmp; \
} /* end switch */ \
GST_CAT_LEVEL_LOG(GST_CAT_DEFAULT, level, sink, "%s HRESULT: %s", t1?t1:"", str_err);
#define CASE_HR_ERR_END(sink, gst_err_msg) \
CASE_HR_DBG_ERR_END(sink, gst_err_msg, GST_LEVEL_ERROR)
#define CASE_HR_DBG_END(sink, gst_err_msg) \
CASE_HR_DBG_ERR_END(sink, gst_err_msg, GST_LEVEL_DEBUG)
#define CHECK_D3D_DEVICE(klass, sink, goto_label) \
if(!klass->d3d.d3d || !klass->d3d.device.d3d_device) { \
GST_ERROR_OBJECT(sink, "Direct3D device or object does not exist"); \
goto goto_label; \
}
#define CHECK_D3D_SWAPCHAIN(sink, goto_label) \
if(!sink->d3d.swapchain) { \
GST_ERROR_OBJECT(sink, "Direct3D swap chain does not exist"); \
goto goto_label; \
}
#define CHECK_D3D_SURFACE(sink, goto_label) \
if(!sink->d3d.surface) { \
GST_ERROR_OBJECT(sink, "NULL D3D offscreen surface"); \
goto goto_label; \
}
#define CHECK_WINDOW_HANDLE(sink, goto_label, is_error) \
if(!sink->d3d.window_handle) { \
GST_CAT_LEVEL_LOG(GST_CAT_DEFAULT, \
(is_error?GST_LEVEL_ERROR:GST_LEVEL_DEBUG), \
sink, "No window handle is set"); \
goto goto_label; \
}
#ifndef D3DFMT_YV12
#define D3DFMT_YV12 MAKEFOURCC ('Y', 'V', '1', '2')
#endif
#ifndef D3DFMT_NV12
#define D3DFMT_NV12 MAKEFOURCC ('N', 'V', '1', '2')
#endif
/** FORMATS **/
#define CASE(x) case x: return #x;
static const gchar *
d3d_format_to_string (D3DFORMAT format)
{
/* Self defined up above */
if (format == D3DFMT_YV12)
return "D3DFMT_YV12";
else if (format == D3DFMT_NV12)
return "D3DFMT_NV12";
switch (format) {
/* From D3D enum */
CASE (D3DFMT_UNKNOWN);
CASE (D3DFMT_X8R8G8B8);
CASE (D3DFMT_YUY2);
CASE (D3DFMT_A8R8G8B8);
CASE (D3DFMT_UYVY);
CASE (D3DFMT_R8G8B8);
CASE (D3DFMT_R5G6B5);
CASE (D3DFMT_X1R5G5B5);
CASE (D3DFMT_A1R5G5B5);
CASE (D3DFMT_A4R4G4B4);
CASE (D3DFMT_R3G3B2);
CASE (D3DFMT_A8);
CASE (D3DFMT_A8R3G3B2);
CASE (D3DFMT_X4R4G4B4);
CASE (D3DFMT_A2B10G10R10);
CASE (D3DFMT_A8B8G8R8);
CASE (D3DFMT_X8B8G8R8);
CASE (D3DFMT_G16R16);
CASE (D3DFMT_A2R10G10B10);
CASE (D3DFMT_A16B16G16R16);
CASE (D3DFMT_A8P8);
CASE (D3DFMT_P8);
CASE (D3DFMT_L8);
CASE (D3DFMT_A8L8);
CASE (D3DFMT_A4L4);
CASE (D3DFMT_V8U8);
CASE (D3DFMT_L6V5U5);
CASE (D3DFMT_X8L8V8U8);
CASE (D3DFMT_Q8W8V8U8);
CASE (D3DFMT_V16U16);
CASE (D3DFMT_A2W10V10U10);
CASE (D3DFMT_DXT1);
CASE (D3DFMT_DXT2);
CASE (D3DFMT_DXT3);
CASE (D3DFMT_DXT4);
CASE (D3DFMT_DXT5);
CASE (D3DFMT_MULTI2_ARGB8);
CASE (D3DFMT_G8R8_G8B8);
CASE (D3DFMT_R8G8_B8G8);
CASE (D3DFMT_D16_LOCKABLE);
CASE (D3DFMT_D32);
CASE (D3DFMT_D15S1);
CASE (D3DFMT_D24S8);
CASE (D3DFMT_D24X8);
CASE (D3DFMT_D24X4S4);
CASE (D3DFMT_D16);
CASE (D3DFMT_L16);
CASE (D3DFMT_D32F_LOCKABLE);
CASE (D3DFMT_D24FS8);
CASE (D3DFMT_VERTEXDATA);
CASE (D3DFMT_INDEX16);
CASE (D3DFMT_INDEX32);
CASE (D3DFMT_Q16W16V16U16);
CASE (D3DFMT_R16F);
CASE (D3DFMT_G16R16F);
CASE (D3DFMT_A16B16G16R16F);
CASE (D3DFMT_R32F);
CASE (D3DFMT_G32R32F);
CASE (D3DFMT_A32B32G32R32F);
CASE (D3DFMT_CxV8U8);
CASE (D3DFMT_FORCE_DWORD);
}
return "UNKNOWN";
}
#undef CASE
static const struct
{
GstVideoFormat gst_format;
D3DFORMAT d3d_format;
} gst_d3d_format_map[] = {
{
GST_VIDEO_FORMAT_BGRx, D3DFMT_X8R8G8B8}, {
GST_VIDEO_FORMAT_RGBx, D3DFMT_X8B8G8R8}, {
GST_VIDEO_FORMAT_BGRA, D3DFMT_A8R8G8B8}, {
GST_VIDEO_FORMAT_RGBA, D3DFMT_A8B8G8R8}, {
GST_VIDEO_FORMAT_BGR, D3DFMT_R8G8B8}, {
GST_VIDEO_FORMAT_RGB16, D3DFMT_R5G6B5}, {
GST_VIDEO_FORMAT_RGB15, D3DFMT_X1R5G5B5}, {
GST_VIDEO_FORMAT_I420, D3DFMT_YV12}, {
GST_VIDEO_FORMAT_YV12, D3DFMT_YV12}, {
GST_VIDEO_FORMAT_NV12, D3DFMT_NV12}, {
GST_VIDEO_FORMAT_YUY2, D3DFMT_YUY2}, {
GST_VIDEO_FORMAT_UYVY, D3DFMT_UYVY}
};
static D3DFORMAT
gst_video_format_to_d3d_format (GstVideoFormat format)
{
gint i;
for (i = 0; i < G_N_ELEMENTS (gst_d3d_format_map); i++)
if (gst_d3d_format_map[i].gst_format == format)
return gst_d3d_format_map[i].d3d_format;
return D3DFMT_UNKNOWN;
}
static gboolean
gst_video_d3d_format_check (GstD3DVideoSink * sink, D3DFORMAT fmt)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
HRESULT hr;
gboolean ret = FALSE;
hr = IDirect3D9_CheckDeviceFormat (klass->d3d.d3d,
klass->d3d.device.adapter,
D3DDEVTYPE_HAL, klass->d3d.device.format, 0, D3DRTYPE_SURFACE, fmt);
if (hr == D3D_OK) {
/* test whether device can perform color-conversion
* from that format to target format
*/
hr = IDirect3D9_CheckDeviceFormatConversion (klass->d3d.d3d,
klass->d3d.device.adapter,
D3DDEVTYPE_HAL, fmt, klass->d3d.device.format);
if (hr == D3D_OK)
ret = TRUE;
}
GST_DEBUG_OBJECT (sink, "Checking: %s - %s", d3d_format_to_string (fmt),
ret ? "TRUE" : "FALSE");
return ret;
}
static gboolean
gst_video_query_d3d_format (GstD3DVideoSink * sink, D3DFORMAT d3dformat)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
/* If it's the display adapter format we don't need to probe */
if (d3dformat == klass->d3d.device.format)
return TRUE;
if (gst_video_d3d_format_check (sink, d3dformat))
return TRUE;
return FALSE;
}
typedef struct
{
GstVideoFormat fmt;
D3DFORMAT d3d_fmt;
gboolean display;
} D3DFormatComp;
static void
d3d_format_comp_free (D3DFormatComp * comp)
{
g_slice_free (D3DFormatComp, comp);
}
static gint
d3d_format_comp_rate (const D3DFormatComp * comp)
{
gint points = 0;
const GstVideoFormatInfo *info;
info = gst_video_format_get_info (comp->fmt);
if (comp->display)
points += 10;
if (GST_VIDEO_FORMAT_INFO_IS_YUV (info))
points += 5;
else if (GST_VIDEO_FORMAT_INFO_IS_RGB (info)) {
guint i, bit_depth = 0;
for (i = 0; i < GST_VIDEO_FORMAT_INFO_N_COMPONENTS (info); i++)
bit_depth += GST_VIDEO_FORMAT_INFO_DEPTH (info, i);
if (bit_depth >= 24)
points += 1;
}
return points;
}
static gint
d3d_format_comp_compare (gconstpointer a, gconstpointer b)
{
gint ptsa = 0, ptsb = 0;
ptsa = d3d_format_comp_rate ((const D3DFormatComp *) a);
ptsb = d3d_format_comp_rate ((const D3DFormatComp *) b);
if (ptsa < ptsb)
return -1;
else if (ptsa == ptsb)
return 0;
else
return 1;
}
#define GST_D3D_SURFACE_MEMORY_NAME "D3DSurface"
typedef struct
{
GstMemory mem;
GstD3DVideoSink *sink;
GMutex lock;
gint map_count;
LPDIRECT3DSURFACE9 surface;
D3DLOCKED_RECT lr;
gint x, y, width, height;
} GstD3DSurfaceMemory;
static GstMemory *
gst_d3d_surface_memory_allocator_alloc (GstAllocator * allocator, gsize size,
GstAllocationParams * params)
{
g_assert_not_reached ();
return NULL;
}
static void
gst_d3d_surface_memory_allocator_free (GstAllocator * allocator,
GstMemory * mem)
{
GstD3DSurfaceMemory *dmem = (GstD3DSurfaceMemory *) mem;
/* If this is a sub-memory, do nothing */
if (mem->parent)
return;
if (dmem->lr.pBits)
g_warning ("d3dvideosink: Freeing memory that is still mapped");
IDirect3DSurface9_Release (dmem->surface);
gst_object_unref (dmem->sink);
g_mutex_clear (&dmem->lock);
g_slice_free (GstD3DSurfaceMemory, dmem);
}
static gpointer
gst_d3d_surface_memory_map (GstMemory * mem, gsize maxsize, GstMapFlags flags)
{
GstD3DSurfaceMemory *parent;
gpointer ret = NULL;
/* find the real parent */
if ((parent = (GstD3DSurfaceMemory *) mem->parent) == NULL)
parent = (GstD3DSurfaceMemory *) mem;
g_mutex_lock (&parent->lock);
if (!parent->map_count
&& IDirect3DSurface9_LockRect (parent->surface, &parent->lr, NULL,
0) != D3D_OK) {
ret = NULL;
goto done;
}
ret = parent->lr.pBits;
parent->map_count++;
done:
g_mutex_unlock (&parent->lock);
return ret;
}
static void
gst_d3d_surface_memory_unmap (GstMemory * mem)
{
GstD3DSurfaceMemory *parent;
/* find the real parent */
if ((parent = (GstD3DSurfaceMemory *) mem->parent) == NULL)
parent = (GstD3DSurfaceMemory *) mem;
g_mutex_lock (&parent->lock);
parent->map_count--;
if (parent->map_count == 0) {
IDirect3DSurface9_UnlockRect (parent->surface);
memset (&parent->lr, 0, sizeof (parent->lr));
}
g_mutex_unlock (&parent->lock);
}
static GstMemory *
gst_d3d_surface_memory_share (GstMemory * mem, gssize offset, gssize size)
{
GstD3DSurfaceMemory *sub;
GstD3DSurfaceMemory *parent;
/* find the real parent */
if ((parent = (GstD3DSurfaceMemory *) mem->parent) == NULL)
parent = (GstD3DSurfaceMemory *) mem;
if (size == -1)
size = mem->size - offset;
sub = g_slice_new0 (GstD3DSurfaceMemory);
/* the shared memory is always readonly */
gst_memory_init (GST_MEMORY_CAST (sub), GST_MINI_OBJECT_FLAGS (parent) |
GST_MINI_OBJECT_FLAG_LOCK_READONLY, mem->allocator,
GST_MEMORY_CAST (parent), mem->maxsize, mem->align, mem->offset + offset,
size);
return GST_MEMORY_CAST (sub);
}
typedef struct
{
GstAllocator parent;
} GstD3DSurfaceMemoryAllocator;
typedef struct
{
GstAllocatorClass parent_class;
} GstD3DSurfaceMemoryAllocatorClass;
GType gst_d3d_surface_memory_allocator_get_type (void);
G_DEFINE_TYPE (GstD3DSurfaceMemoryAllocator, gst_d3d_surface_memory_allocator,
GST_TYPE_ALLOCATOR);
#define GST_TYPE_D3D_SURFACE_MEMORY_ALLOCATOR (gst_d3d_surface_memory_allocator_get_type())
#define GST_IS_D3D_SURFACE_MEMORY_ALLOCATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_D3D_SURFACE_MEMORY_ALLOCATOR))
static void
gst_d3d_surface_memory_allocator_class_init (GstD3DSurfaceMemoryAllocatorClass *
klass)
{
GstAllocatorClass *allocator_class;
allocator_class = (GstAllocatorClass *) klass;
allocator_class->alloc = gst_d3d_surface_memory_allocator_alloc;
allocator_class->free = gst_d3d_surface_memory_allocator_free;
}
static void
gst_d3d_surface_memory_allocator_init (GstD3DSurfaceMemoryAllocator * allocator)
{
GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
alloc->mem_type = GST_D3D_SURFACE_MEMORY_NAME;
alloc->mem_map = gst_d3d_surface_memory_map;
alloc->mem_unmap = gst_d3d_surface_memory_unmap;
alloc->mem_share = gst_d3d_surface_memory_share;
/* fallback copy */
GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
}
G_DEFINE_TYPE (GstD3DSurfaceBufferPool, gst_d3dsurface_buffer_pool,
GST_TYPE_VIDEO_BUFFER_POOL);
GstBufferPool *
gst_d3dsurface_buffer_pool_new (GstD3DVideoSink * sink)
{
GstD3DSurfaceBufferPool *pool;
pool = g_object_new (GST_TYPE_D3DSURFACE_BUFFER_POOL, NULL);
pool->sink = gst_object_ref (sink);
GST_LOG_OBJECT (pool, "new buffer pool %p", pool);
return GST_BUFFER_POOL_CAST (pool);
}
static void
gst_d3dsurface_buffer_pool_finalize (GObject * object)
{
GstD3DSurfaceBufferPool *pool = GST_D3DSURFACE_BUFFER_POOL_CAST (object);
GST_LOG_OBJECT (pool, "finalize buffer pool %p", pool);
gst_object_unref (pool->sink);
if (pool->allocator)
gst_object_unref (pool->allocator);
G_OBJECT_CLASS (gst_d3dsurface_buffer_pool_parent_class)->finalize (object);
}
static const gchar **
gst_d3dsurface_buffer_pool_get_options (GstBufferPool * pool)
{
static const gchar *options[] = { GST_BUFFER_POOL_OPTION_VIDEO_META, NULL };
return options;
}
static gboolean
gst_d3dsurface_buffer_pool_set_config (GstBufferPool * bpool,
GstStructure * config)
{
GstD3DSurfaceBufferPool *pool = GST_D3DSURFACE_BUFFER_POOL_CAST (bpool);
GstCaps *caps;
GstVideoInfo info;
if (!GST_BUFFER_POOL_CLASS
(gst_d3dsurface_buffer_pool_parent_class)->set_config (bpool, config)) {
return FALSE;
}
if (!gst_buffer_pool_config_get_params (config, &caps, NULL, NULL, NULL)
|| !caps) {
GST_ERROR_OBJECT (pool, "Buffer pool configuration without caps");
return FALSE;
}
/* now parse the caps from the config */
if (!gst_video_info_from_caps (&info, caps)) {
GST_ERROR_OBJECT (pool, "Failed to parse caps %" GST_PTR_FORMAT, caps);
return FALSE;
}
if (gst_video_format_to_d3d_format (GST_VIDEO_INFO_FORMAT (&info)) ==
D3DFMT_UNKNOWN) {
GST_ERROR_OBJECT (pool, "Unsupported video format in caps %" GST_PTR_FORMAT,
caps);
return FALSE;
}
GST_LOG_OBJECT (pool, "%dx%d, caps %" GST_PTR_FORMAT, info.width, info.height,
caps);
pool->info = info;
pool->add_metavideo =
gst_buffer_pool_config_has_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
if (pool->add_metavideo)
pool->allocator =
g_object_new (GST_TYPE_D3D_SURFACE_MEMORY_ALLOCATOR, NULL);
return TRUE;
}
static GstFlowReturn
gst_d3dsurface_buffer_pool_alloc_buffer (GstBufferPool * bpool,
GstBuffer ** buffer, GstBufferPoolAcquireParams * params)
{
GstD3DSurfaceBufferPool *pool = GST_D3DSURFACE_BUFFER_POOL_CAST (bpool);
GstD3DVideoSink *sink = pool->sink;
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
GstD3DSurfaceMemory *mem;
LPDIRECT3DSURFACE9 surface;
D3DFORMAT d3dformat;
gint stride[GST_VIDEO_MAX_PLANES] = { 0, };
gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
D3DLOCKED_RECT lr;
HRESULT hr;
gsize size = 0;
*buffer = NULL;
if (!pool->add_metavideo) {
GST_DEBUG_OBJECT (pool, "No video meta allowed, fallback alloc");
goto fallback;
}
d3dformat =
gst_video_format_to_d3d_format (GST_VIDEO_INFO_FORMAT (&pool->info));
hr = IDirect3DDevice9_CreateOffscreenPlainSurface (klass->d3d.
device.d3d_device, GST_VIDEO_INFO_WIDTH (&pool->info),
GST_VIDEO_INFO_HEIGHT (&pool->info), d3dformat, D3DPOOL_DEFAULT, &surface,
NULL);
if (hr != D3D_OK) {
GST_ERROR_OBJECT (sink, "Failed to create D3D surface");
goto fallback;
}
IDirect3DSurface9_LockRect (surface, &lr, NULL, 0);
if (!lr.pBits) {
GST_ERROR_OBJECT (sink, "Failed to lock D3D surface");
IDirect3DSurface9_Release (surface);
goto fallback;
}
switch (GST_VIDEO_INFO_FORMAT (&pool->info)) {
case GST_VIDEO_FORMAT_BGR:
offset[0] = 0;
stride[0] = lr.Pitch;
size = lr.Pitch * GST_VIDEO_INFO_HEIGHT (&pool->info) * 3;
break;
case GST_VIDEO_FORMAT_BGRx:
case GST_VIDEO_FORMAT_RGBx:
case GST_VIDEO_FORMAT_BGRA:
case GST_VIDEO_FORMAT_RGBA:
offset[0] = 0;
stride[0] = lr.Pitch;
size = lr.Pitch * GST_VIDEO_INFO_HEIGHT (&pool->info) * 4;
break;
case GST_VIDEO_FORMAT_RGB16:
case GST_VIDEO_FORMAT_RGB15:
offset[0] = 0;
stride[0] = lr.Pitch;
size = lr.Pitch * GST_VIDEO_INFO_HEIGHT (&pool->info) * 2;
break;
case GST_VIDEO_FORMAT_YUY2:
case GST_VIDEO_FORMAT_UYVY:
offset[0] = 0;
stride[0] = lr.Pitch;
size = lr.Pitch * GST_VIDEO_INFO_HEIGHT (&pool->info) * 2;
break;
case GST_VIDEO_FORMAT_I420:
case GST_VIDEO_FORMAT_YV12:
offset[0] = 0;
stride[0] = lr.Pitch;
if (GST_VIDEO_INFO_FORMAT (&pool->info) == GST_VIDEO_FORMAT_YV12) {
offset[1] =
offset[0] + stride[0] * GST_VIDEO_INFO_COMP_HEIGHT (&pool->info, 0);
stride[1] = lr.Pitch / 2;
offset[2] =
offset[1] + stride[1] * GST_VIDEO_INFO_COMP_HEIGHT (&pool->info, 1);
stride[2] = lr.Pitch / 2;
size =
offset[2] + stride[2] * GST_VIDEO_INFO_COMP_HEIGHT (&pool->info, 2);
} else {
offset[2] =
offset[0] + stride[0] * GST_VIDEO_INFO_COMP_HEIGHT (&pool->info, 0);
stride[2] = lr.Pitch / 2;
offset[1] =
offset[2] + stride[2] * GST_VIDEO_INFO_COMP_HEIGHT (&pool->info, 2);
stride[1] = lr.Pitch / 2;
size =
offset[1] + stride[1] * GST_VIDEO_INFO_COMP_HEIGHT (&pool->info, 1);
}
break;
case GST_VIDEO_FORMAT_NV12:
offset[0] = 0;
stride[0] = lr.Pitch;
offset[1] =
offset[0] + stride[0] * GST_VIDEO_INFO_COMP_HEIGHT (&pool->info, 0);
stride[1] = lr.Pitch;
size =
offset[1] + stride[1] * GST_VIDEO_INFO_COMP_HEIGHT (&pool->info, 1);
break;
default:
g_assert_not_reached ();
break;
}
IDirect3DSurface9_UnlockRect (surface);
*buffer = gst_buffer_new ();
gst_buffer_add_video_meta_full (*buffer, GST_VIDEO_FRAME_FLAG_NONE,
GST_VIDEO_INFO_FORMAT (&pool->info), GST_VIDEO_INFO_WIDTH (&pool->info),
GST_VIDEO_INFO_HEIGHT (&pool->info),
GST_VIDEO_INFO_N_PLANES (&pool->info), offset, stride);
mem = g_slice_new0 (GstD3DSurfaceMemory);
gst_memory_init (GST_MEMORY_CAST (mem), 0, pool->allocator, NULL, size, 0, 0,
size);
mem->surface = surface;
mem->sink = gst_object_ref (sink);
mem->x = mem->y = 0;
mem->width = GST_VIDEO_INFO_WIDTH (&pool->info);
mem->height = GST_VIDEO_INFO_HEIGHT (&pool->info);
g_mutex_init (&mem->lock);
gst_buffer_append_memory (*buffer, GST_MEMORY_CAST (mem));
return GST_FLOW_OK;
fallback:
{
return
GST_BUFFER_POOL_CLASS
(gst_d3dsurface_buffer_pool_parent_class)->alloc_buffer (bpool, buffer,
params);
}
}
static void
gst_d3dsurface_buffer_pool_release_buffer (GstBufferPool * bpool,
GstBuffer * buffer)
{
GstMemory *mem = NULL;
/* Check if something replaced our memory */
if (gst_buffer_n_memory (buffer) != 1 ||
(mem = gst_buffer_peek_memory (buffer, 0)) == 0 ||
!gst_memory_is_type (mem, GST_D3D_SURFACE_MEMORY_NAME)) {
gst_buffer_unref (buffer);
return;
}
return GST_BUFFER_POOL_CLASS
(gst_d3dsurface_buffer_pool_parent_class)->release_buffer (bpool, buffer);
}
static void
gst_d3dsurface_buffer_pool_class_init (GstD3DSurfaceBufferPoolClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstBufferPoolClass *gstbufferpool_class = (GstBufferPoolClass *) klass;
gobject_class->finalize = gst_d3dsurface_buffer_pool_finalize;
gstbufferpool_class->get_options = gst_d3dsurface_buffer_pool_get_options;
gstbufferpool_class->set_config = gst_d3dsurface_buffer_pool_set_config;
gstbufferpool_class->alloc_buffer = gst_d3dsurface_buffer_pool_alloc_buffer;
gstbufferpool_class->release_buffer =
gst_d3dsurface_buffer_pool_release_buffer;
}
static void
gst_d3dsurface_buffer_pool_init (GstD3DSurfaceBufferPool * pool)
{
}
GstCaps *
d3d_supported_caps (GstD3DVideoSink * sink)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
int i;
GList *fmts = NULL, *l;
GstCaps *caps = NULL;
GstVideoFormat gst_format;
D3DFORMAT d3d_format;
GValue va = { 0, };
GValue v = { 0, };
LOCK_SINK (sink);
if (sink->supported_caps) {
caps = gst_caps_ref (sink->supported_caps);
goto unlock;
}
LOCK_CLASS (sink, klass);
if (klass->d3d.refs == 0) {
UNLOCK_CLASS (sink, klass);
goto unlock;
}
UNLOCK_CLASS (sink, klass);
for (i = 0; i < G_N_ELEMENTS (gst_d3d_format_map); i++) {
D3DFormatComp *comp;
gst_format = gst_d3d_format_map[i].gst_format;
d3d_format = gst_d3d_format_map[i].d3d_format;
if (!gst_video_query_d3d_format (sink, d3d_format))
continue;
comp = g_slice_new0 (D3DFormatComp);
comp->fmt = (GstVideoFormat) gst_format;
comp->d3d_fmt = d3d_format;
comp->display = (d3d_format == klass->d3d.device.format);
fmts = g_list_insert_sorted (fmts, comp, d3d_format_comp_compare);
}
GST_DEBUG_OBJECT (sink, "Supported Caps:");
g_value_init (&va, GST_TYPE_LIST);
g_value_init (&v, G_TYPE_STRING);
for (l = fmts; l; l = g_list_next (l)) {
D3DFormatComp *comp = (D3DFormatComp *) l->data;
GST_DEBUG_OBJECT (sink, "%s -> %s %s",
gst_video_format_to_string (comp->fmt),
d3d_format_to_string (comp->d3d_fmt), comp->display ? "[display]" : "");
g_value_set_string (&v, gst_video_format_to_string (comp->fmt));
gst_value_list_append_value (&va, &v);
}
caps = gst_caps_new_simple ("video/x-raw",
"width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
"height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
"framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
gst_caps_set_value (caps, "format", &va);
g_value_unset (&v);
g_value_unset (&va);
g_list_free_full (fmts, (GDestroyNotify) d3d_format_comp_free);
sink->supported_caps = gst_caps_ref (caps);
#ifndef GST_DISABLE_GST_DEBUG
{
gchar *tmp = gst_caps_to_string (caps);
GST_DEBUG_OBJECT (sink, "Supported caps: %s", tmp);
g_free (tmp);
}
#endif
unlock:
UNLOCK_SINK (sink);
return caps;
}
gboolean
d3d_set_render_format (GstD3DVideoSink * sink)
{
D3DFORMAT fmt;
gboolean ret = FALSE;
LOCK_SINK (sink);
fmt = gst_video_format_to_d3d_format (sink->format);
if (fmt == D3DFMT_UNKNOWN) {
GST_ERROR_OBJECT (sink, "Unsupported video format %s",
gst_video_format_to_string (sink->format));
goto end;
}
if (!gst_video_query_d3d_format (sink, fmt)) {
GST_ERROR_OBJECT (sink, "Failed to query a D3D render format for %s",
gst_video_format_to_string (sink->format));
goto end;
}
GST_DEBUG_OBJECT (sink, "Selected %s -> %s",
gst_video_format_to_string (sink->format), d3d_format_to_string (fmt));
sink->d3d.format = fmt;
ret = TRUE;
end:
UNLOCK_SINK (sink);
return ret;
}
static gboolean
d3d_get_hwnd_window_size (HWND hwnd, gint * width, gint * height)
{
RECT sz;
g_return_val_if_fail (width != NULL, FALSE);
g_return_val_if_fail (height != NULL, FALSE);
*width = 0;
*height = 0;
if (!hwnd)
return FALSE;
GetClientRect (hwnd, &sz);
*width = MAX (1, ABS (sz.right - sz.left));
*height = MAX (1, ABS (sz.bottom - sz.top));
return TRUE;
}
static gboolean
d3d_get_render_rects (GstVideoRectangle * rr, RECT * dst, RECT * src)
{
if (!rr)
return FALSE;
/* Rect on target */
if (dst) {
dst->left = rr->x;
dst->top = rr->y;
dst->right = rr->x + rr->w;
dst->bottom = rr->y + rr->h;
}
/* Rect on source */
if (src) {
src->left = 0;
src->top = 0;
src->right = rr->w;
src->bottom = rr->h;
}
return TRUE;
}
static gboolean
d3d_get_render_coordinates (GstD3DVideoSink * sink, gint in_x, gint in_y,
gdouble * out_x, gdouble * out_y)
{
GstVideoRectangle r_area;
gdouble tmp;
gboolean ret = FALSE;
g_return_val_if_fail (out_x != NULL, FALSE);
g_return_val_if_fail (out_y != NULL, FALSE);
LOCK_SINK (sink);
CHECK_WINDOW_HANDLE (sink, end, FALSE);
/* Get renderable area of the window */
if (sink->d3d.render_rect) {
memcpy (&r_area, sink->d3d.render_rect, sizeof (r_area));
} else {
memset (&r_area, 0, sizeof (r_area));
d3d_get_hwnd_window_size (sink->d3d.window_handle, &r_area.w, &r_area.h);
}
/* If window coords outside render area.. return */
if (in_x < r_area.x || in_x > r_area.x + r_area.w ||
in_y < r_area.y || in_y > r_area.y + r_area.h)
goto end;
/* Convert window coordinates to source frame pixel coordinates */
if (sink->force_aspect_ratio) {
GstVideoRectangle tmp = { 0, 0, 0, 0 };
GstVideoRectangle dst = { 0, 0, 0, 0 };
tmp.w = GST_VIDEO_SINK_WIDTH (sink);
tmp.h = GST_VIDEO_SINK_HEIGHT (sink);
gst_video_sink_center_rect (tmp, r_area, &dst, TRUE);
r_area.x = r_area.x + dst.x;
r_area.y = r_area.y + dst.y;
r_area.w = dst.w;
r_area.h = dst.h;
/* If window coords outside render area.. return */
if (in_x < r_area.x || in_x > (r_area.x + r_area.w) ||
in_y < r_area.y || in_y > (r_area.y + r_area.h))
goto end;
}
tmp = in_x - r_area.x;
if (r_area.w == GST_VIDEO_SINK_WIDTH (sink))
*out_x = tmp;
else if (r_area.w > GST_VIDEO_SINK_WIDTH (sink))
*out_x =
((gdouble) tmp / ((gdouble) r_area.w /
(gdouble) GST_VIDEO_SINK_WIDTH (sink)));
else
*out_x =
((gdouble) GST_VIDEO_SINK_WIDTH (sink) / (gdouble) r_area.w) *
(gdouble) tmp;
tmp = in_y - r_area.y;
if (r_area.h == GST_VIDEO_SINK_HEIGHT (sink))
*out_y = tmp;
else if (r_area.h > GST_VIDEO_SINK_HEIGHT (sink))
*out_y =
((gdouble) tmp / ((gdouble) r_area.h /
(gdouble) GST_VIDEO_SINK_HEIGHT (sink)));
else
*out_y =
((gdouble) GST_VIDEO_SINK_HEIGHT (sink) / (gdouble) r_area.h) *
(gdouble) tmp;
ret = TRUE;
end:
UNLOCK_SINK (sink);
return ret;
}
/** Windows for rendering (User Set or Internal) **/
static void
d3d_window_wndproc_unset (GstD3DVideoSink * sink)
{
WNDPROC cur_wnd_proc = NULL;
LOCK_SINK (sink);
GST_DEBUG_OBJECT (sink, " ");
if (sink->d3d.window_handle == NULL) {
GST_WARNING_OBJECT (sink, "D3D window_handle is NULL");
goto end;
}
cur_wnd_proc =
(WNDPROC) GetWindowLongPtr (sink->d3d.window_handle, GWLP_WNDPROC);
if (cur_wnd_proc != d3d_wnd_proc) {
GST_WARNING_OBJECT (sink, "D3D window proc is not set on current window");
goto end;
}
if (sink->d3d.orig_wnd_proc == NULL) {
GST_WARNING_OBJECT (sink, "D3D orig window proc is NULL, can not restore");
goto end;
}
/* Restore orignal WndProc for window_handle */
if (!SetWindowLongPtr (sink->d3d.window_handle, GWLP_WNDPROC,
(LONG_PTR) sink->d3d.orig_wnd_proc)) {
GST_WARNING_OBJECT (sink, "D3D failed to set original WndProc");
goto end;
}
end:
sink->d3d.orig_wnd_proc = NULL;
sink->d3d.window_handle = NULL;
UNLOCK_SINK (sink);
}
static gboolean
d3d_window_wndproc_set (GstD3DVideoSink * sink)
{
WNDPROC cur_wnd_proc;
gboolean ret = FALSE;
LOCK_SINK (sink);
cur_wnd_proc =
(WNDPROC) GetWindowLongPtr (sink->d3d.window_handle, GWLP_WNDPROC);
if (cur_wnd_proc != NULL && cur_wnd_proc == d3d_wnd_proc) {
GST_DEBUG_OBJECT (sink,
"D3D window proc func is already set on the current window");
ret = TRUE;
goto end;
}
/* Store the original window proc function */
sink->d3d.orig_wnd_proc =
(WNDPROC) SetWindowLongPtr (sink->d3d.window_handle, GWLP_WNDPROC,
(LONG_PTR) d3d_wnd_proc);
/* Note: If the window belongs to another process this will fail */
if (sink->d3d.orig_wnd_proc == NULL) {
GST_ERROR_OBJECT (sink,
"Failed to set WndProc function on window. Error: %d",
(gint) GetLastError ());
goto end;
}
/* Make sink accessible to d3d_wnd_proc */
SetProp (sink->d3d.window_handle, TEXT ("GstD3DVideoSink"), sink);
ret = TRUE;
end:
UNLOCK_SINK (sink);
return ret;
}
static void
d3d_prepare_render_window (GstD3DVideoSink * sink)
{
LOCK_SINK (sink);
if (sink->d3d.window_handle == NULL) {
GST_DEBUG_OBJECT (sink, "No window handle has been set.");
goto end;
}
if (sink->d3d.device_lost) {
GST_DEBUG_OBJECT (sink, "Device is lost, waiting for reset.");
goto end;
}
if (d3d_init_swap_chain (sink, sink->d3d.window_handle)) {
d3d_window_wndproc_set (sink);
sink->d3d.renderable = TRUE;
GST_DEBUG_OBJECT (sink, "Prepared window for render [HWND:%p]",
sink->d3d.window_handle);
} else {
GST_ERROR_OBJECT (sink, "Failed preparing window for render [HWND:%p]",
sink->d3d.window_handle);
}
end:
UNLOCK_SINK (sink);
}
void
d3d_set_window_handle (GstD3DVideoSink * sink, guintptr window_id,
gboolean is_internal)
{
LOCK_SINK (sink);
if (sink->d3d.window_handle == (HWND) window_id) {
GST_WARNING_OBJECT (sink, "Window HWND already set to: %" G_GUINTPTR_FORMAT,
window_id);
goto end;
}
/* Unset current window */
if (sink->d3d.window_handle != NULL) {
PostMessage (sink->d3d.window_handle, WM_QUIT_THREAD, 0, 0);
GST_DEBUG_OBJECT (sink, "Unsetting window [HWND:%p]",
sink->d3d.window_handle);
d3d_window_wndproc_unset (sink);
d3d_release_swap_chain (sink);
sink->d3d.window_handle = NULL;
sink->d3d.window_is_internal = FALSE;
sink->d3d.renderable = FALSE;
}
/* Set new one */
if (window_id) {
sink->d3d.window_handle = (HWND) window_id;
sink->d3d.window_is_internal = is_internal;
if (!is_internal)
sink->d3d.external_window_handle = sink->d3d.window_handle;
/* If caps have been set.. prepare window */
if (sink->format != 0)
d3d_prepare_render_window (sink);
}
end:
UNLOCK_SINK (sink);
}
void
d3d_set_render_rectangle (GstD3DVideoSink * sink)
{
LOCK_SINK (sink);
/* Setting the pointer lets us know render rect is set */
sink->d3d.render_rect = &sink->render_rect;
d3d_resize_swap_chain (sink);
d3d_present_swap_chain (sink);
UNLOCK_SINK (sink);
}
void
d3d_expose_window (GstD3DVideoSink * sink)
{
GST_DEBUG_OBJECT (sink, "EXPOSE");
d3d_present_swap_chain (sink);
}
gboolean
d3d_prepare_window (GstD3DVideoSink * sink)
{
HWND hWnd;
gboolean ret = FALSE;
LOCK_SINK (sink);
/* if we already had an external window, then use it again */
if (sink->d3d.external_window_handle)
sink->d3d.window_handle = sink->d3d.external_window_handle;
/* Give the app a last chance to set a window id */
if (!sink->d3d.window_handle)
gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (sink));
/* If the user did not set a window id .. check if we should create one */
if (!sink->d3d.window_handle) {
if (sink->create_internal_window) {
if ((hWnd = d3d_create_internal_window (sink))) {
GST_DEBUG_OBJECT (sink,
"No window id was set.. creating internal window");
d3d_set_window_handle (sink, (guintptr) hWnd, TRUE);
} else {
GST_ERROR_OBJECT (sink, "Failed to create internal window");
goto end;
}
} else {
GST_DEBUG_OBJECT (sink, "No window id is set..");
goto end;
}
} else {
d3d_prepare_render_window (sink);
}
ret = TRUE;
end:
UNLOCK_SINK (sink);
return ret;
}
gboolean
d3d_stop (GstD3DVideoSink * sink)
{
if (sink->pool)
gst_buffer_pool_set_active (sink->pool, FALSE);
if (sink->fallback_pool)
gst_buffer_pool_set_active (sink->fallback_pool, FALSE);
gst_object_replace ((GstObject **) & sink->pool, NULL);
gst_object_replace ((GstObject **) & sink->fallback_pool, NULL);
gst_buffer_replace (&sink->fallback_buffer, NULL);
/* Release D3D resources */
d3d_set_window_handle (sink, 0, FALSE);
return TRUE;
}
/** D3D Lost and Reset Device **/
static void
d3d_notify_device_lost (GstD3DVideoSink * sink)
{
gboolean notify = FALSE;
g_return_if_fail (GST_IS_D3DVIDEOSINK (sink));
LOCK_SINK (sink);
if (!sink->d3d.device_lost) {
GST_WARNING_OBJECT (sink,
"D3D Device has been lost. Cleanup up resources..");
/* Stream will continue with GST_FLOW_OK, until device has been reset */
sink->d3d.device_lost = TRUE;
/* First we clean up all resources in this d3dvideo instance */
d3d_release_swap_chain (sink);
/* Notify our hidden thread */
notify = TRUE;
}
UNLOCK_SINK (sink);
if (notify)
d3d_class_notify_device_lost (sink);
}
static void
d3d_notify_device_reset (GstD3DVideoSink * sink)
{
LOCK_SINK (sink);
if (sink->d3d.device_lost) {
GST_DEBUG_OBJECT (sink,
"D3D Device has been reset. Re-init swap chain if still streaming");
/* If we're still streaming.. reset swap chain */
if (sink->d3d.window_handle != NULL)
d3d_init_swap_chain (sink, sink->d3d.window_handle);
sink->d3d.device_lost = FALSE;
}
UNLOCK_SINK (sink);
}
/** Swap Chains **/
static gboolean
d3d_init_swap_chain (GstD3DVideoSink * sink, HWND hWnd)
{
D3DPRESENT_PARAMETERS present_params;
LPDIRECT3DSWAPCHAIN9 d3d_swapchain = NULL;
D3DTEXTUREFILTERTYPE d3d_filtertype;
HRESULT hr;
GstD3DVideoSinkClass *klass;
gboolean ret = FALSE;
g_return_val_if_fail (sink != NULL, FALSE);
klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
g_return_val_if_fail (klass != NULL, FALSE);
LOCK_SINK (sink);
LOCK_CLASS (sink, klass);
/* We need a display device */
CHECK_D3D_DEVICE (klass, sink, error);
GST_DEBUG ("Initializing Direct3D swap chain");
GST_DEBUG ("Direct3D back buffer size: %dx%d", GST_VIDEO_SINK_WIDTH (sink),
GST_VIDEO_SINK_HEIGHT (sink));
/* When windowed, width and height determined by HWND */
ZeroMemory (&present_params, sizeof (present_params));
present_params.Windowed = TRUE;
present_params.SwapEffect = D3DSWAPEFFECT_DISCARD; /* D3DSWAPEFFECT_COPY */
present_params.hDeviceWindow = hWnd;
present_params.BackBufferFormat = klass->d3d.device.format;
hr = IDirect3DDevice9_CreateAdditionalSwapChain (klass->d3d.device.d3d_device,
&present_params, &d3d_swapchain);
ERROR_CHECK_HR (hr) {
CASE_HR_ERR (D3DERR_NOTAVAILABLE);
CASE_HR_ERR (D3DERR_DEVICELOST);
CASE_HR_ERR (D3DERR_INVALIDCALL);
CASE_HR_ERR (D3DERR_OUTOFVIDEOMEMORY);
CASE_HR_ERR (E_OUTOFMEMORY);
CASE_HR_ERR_END (sink, "Error creating D3D swapchian");
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.
*/
hr = IDirect3D9_CheckDeviceFormat (klass->d3d.d3d,
klass->d3d.device.adapter,
D3DDEVTYPE_HAL,
klass->d3d.device.format,
D3DUSAGE_QUERY_FILTER, D3DRTYPE_TEXTURE, sink->d3d.format);
if (hr == D3D_OK)
d3d_filtertype = klass->d3d.device.filter_type;
else
d3d_filtertype = D3DTEXF_NONE;
GST_DEBUG ("Direct3D stretch rect texture filter: %d", d3d_filtertype);
sink->d3d.filtertype = d3d_filtertype;
if (sink->d3d.swapchain != NULL)
IDirect3DSwapChain9_Release (sink->d3d.swapchain);
sink->d3d.swapchain = d3d_swapchain;
ret = TRUE;
error:
if (!ret) {
if (d3d_swapchain)
IDirect3DSwapChain9_Release (d3d_swapchain);
}
UNLOCK_CLASS (sink, klass);
UNLOCK_SINK (sink);
return ret;
}
static gboolean
d3d_release_swap_chain (GstD3DVideoSink * sink)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
int ref_count;
gboolean ret = FALSE;
LOCK_SINK (sink);
GST_DEBUG_OBJECT (sink, "Releasing Direct3D swap chain");
CHECK_D3D_DEVICE (klass, sink, end);
if (!sink->d3d.swapchain) {
ret = TRUE;
goto end;
}
gst_buffer_replace (&sink->fallback_buffer, NULL);
if (sink->fallback_pool)
gst_buffer_pool_set_active (sink->fallback_pool, FALSE);
if (sink->d3d.swapchain) {
ref_count = IDirect3DSwapChain9_Release (sink->d3d.swapchain);
sink->d3d.swapchain = NULL;
GST_DEBUG_OBJECT (sink, "D3D swapchain released. Ref count: %d", ref_count);
}
if (sink->d3d.surface) {
ref_count = IDirect3DSurface9_Release (sink->d3d.surface);
sink->d3d.surface = NULL;
GST_DEBUG_OBJECT (sink, "D3D surface released. Ref count: %d", ref_count);
}
ret = TRUE;
end:
UNLOCK_SINK (sink);
return ret;
}
static gboolean
d3d_resize_swap_chain (GstD3DVideoSink * sink)
{
GstD3DVideoSinkClass *klass;
D3DPRESENT_PARAMETERS d3d_pp;
LPDIRECT3DSWAPCHAIN9 swapchain = NULL;
gint w = 0, h = 0, ref_count = 0;
gboolean ret = FALSE;
HRESULT hr;
gboolean need_new = FALSE;
int clip_ret;
HDC handle_hdc;
RECT clip_rectangle;
g_return_val_if_fail (sink != NULL, FALSE);
klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
g_return_val_if_fail (klass != NULL, FALSE);
LOCK_SINK (sink);
if (!sink->d3d.renderable || sink->d3d.device_lost) {
UNLOCK_SINK (sink);
return FALSE;
}
LOCK_CLASS (sink, klass);
CHECK_WINDOW_HANDLE (sink, end, FALSE);
CHECK_D3D_DEVICE (klass, sink, end);
CHECK_D3D_SWAPCHAIN (sink, end);
handle_hdc = GetDC (sink->d3d.window_handle);
clip_ret = GetClipBox (handle_hdc, &clip_rectangle);
ReleaseDC (sink->d3d.window_handle, handle_hdc);
if (clip_ret == NULLREGION) {
GST_DEBUG_OBJECT (sink, "Window is hidden, not resizing swapchain");
UNLOCK_CLASS (sink, klass);
UNLOCK_SINK (sink);
return TRUE;
}
d3d_get_hwnd_window_size (sink->d3d.window_handle, &w, &h);
ZeroMemory (&d3d_pp, sizeof (d3d_pp));
/* Get the parameters used to create this swap chain */
hr = IDirect3DSwapChain9_GetPresentParameters (sink->d3d.swapchain, &d3d_pp);
if (hr != D3D_OK) {
GST_ERROR_OBJECT (sink,
"Unable to determine Direct3D present parameters for swap chain");
goto end;
}
/* Reisze needed? */
if (d3d_pp.BackBufferWidth != w || d3d_pp.BackBufferHeight != h)
need_new = TRUE;
#if 0
/* Render rect set or unset? */
if ((d3d_pp.SwapEffect != D3DSWAPEFFECT_COPY && sink->d3d.render_rect) ||
(d3d_pp.SwapEffect != D3DSWAPEFFECT_DISCARD
&& sink->d3d.render_rect == NULL)) {
d3d_pp.SwapEffect =
(sink->d3d.render_rect ==
NULL) ? D3DSWAPEFFECT_DISCARD : D3DSWAPEFFECT_COPY;
GST_DEBUG_OBJECT (sink, "Setting SwapEffect: %s",
sink->d3d.render_rect ? "COPY" : "DISCARD");
need_new = TRUE;
}
#endif
if (!need_new) {
ret = TRUE;
goto end;
}
GST_DEBUG_OBJECT (sink, "Resizing swapchain %dx%d to %dx%d",
d3d_pp.BackBufferWidth, d3d_pp.BackBufferHeight, w, h);
/* As long as present params windowed == TRUE, width or height
* of 0 will force use of HWND's size.
*/
d3d_pp.BackBufferWidth = 0;
d3d_pp.BackBufferHeight = 0;
/* Release current swapchain */
if (sink->d3d.swapchain != NULL) {
ref_count = IDirect3DSwapChain9_Release (sink->d3d.swapchain);
if (ref_count > 0) {
GST_WARNING_OBJECT (sink, "Release swapchain refcount: %d", ref_count);
}
sink->d3d.swapchain = NULL;
}
hr = IDirect3DDevice9_CreateAdditionalSwapChain (klass->d3d.device.d3d_device,
&d3d_pp, &swapchain);
ERROR_CHECK_HR (hr) {
CASE_HR_ERR (D3DERR_NOTAVAILABLE);
CASE_HR_ERR (D3DERR_DEVICELOST);
CASE_HR_ERR (D3DERR_INVALIDCALL);
CASE_HR_ERR (D3DERR_OUTOFVIDEOMEMORY);
CASE_HR_ERR (E_OUTOFMEMORY);
CASE_HR_ERR_END (sink, "Error creating swapchian");
goto end;
}
sink->d3d.swapchain = swapchain;
ret = TRUE;
end:
UNLOCK_CLASS (sink, klass);
UNLOCK_SINK (sink);
return ret;
}
static gboolean
d3d_copy_buffer (GstD3DVideoSink * sink, GstBuffer * from, GstBuffer * to)
{
gboolean ret = FALSE;
GstVideoFrame from_frame, to_frame;
memset (&from_frame, 0, sizeof (from_frame));
memset (&to_frame, 0, sizeof (to_frame));
LOCK_SINK (sink);
if (!sink->d3d.renderable || sink->d3d.device_lost)
goto end;
if (!gst_video_frame_map (&from_frame, &sink->info, from, GST_MAP_READ) ||
!gst_video_frame_map (&to_frame, &sink->info, to, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (sink, "NULL GstBuffer");
goto end;
}
switch (sink->format) {
case GST_VIDEO_FORMAT_YUY2:
case GST_VIDEO_FORMAT_UYVY:{
const guint8 *src;
guint8 *dst;
gint dststride, srcstride;
gint i, h, w;
src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, 0);
dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, 0);
srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, 0);
dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, 0);
h = GST_VIDEO_FRAME_HEIGHT (&from_frame);
w = GST_ROUND_UP_4 (GST_VIDEO_FRAME_WIDTH (&from_frame) * 2);
for (i = 0; i < h; i++) {
memcpy (dst, src, w);
dst += dststride;
src += srcstride;
}
break;
}
case GST_VIDEO_FORMAT_I420:
case GST_VIDEO_FORMAT_YV12:{
const guint8 *src;
guint8 *dst;
gint srcstride, dststride;
gint i, j, h_, w_;
for (i = 0; i < 3; i++) {
src = GST_VIDEO_FRAME_COMP_DATA (&from_frame, i);
dst = GST_VIDEO_FRAME_COMP_DATA (&to_frame, i);
srcstride = GST_VIDEO_FRAME_COMP_STRIDE (&from_frame, i);
dststride = GST_VIDEO_FRAME_COMP_STRIDE (&to_frame, i);
h_ = GST_VIDEO_FRAME_COMP_HEIGHT (&from_frame, i);
w_ = GST_VIDEO_FRAME_COMP_WIDTH (&from_frame, i);
for (j = 0; j < h_; j++) {
memcpy (dst, src, w_);
dst += dststride;
src += srcstride;
}
}
break;
}
case GST_VIDEO_FORMAT_NV12:{
const guint8 *src;
guint8 *dst;
gint srcstride, dststride;
gint i, j, h_, w_;
for (i = 0; i < 2; i++) {
src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, i);
dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, i);
srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, i);
dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, i);
h_ = GST_VIDEO_FRAME_COMP_HEIGHT (&from_frame, i);
w_ = GST_VIDEO_FRAME_COMP_WIDTH (&from_frame, i);
for (j = 0; j < h_; j++) {
memcpy (dst, src, w_ * 2);
dst += dststride;
src += srcstride;
}
}
break;
}
case GST_VIDEO_FORMAT_BGRA:
case GST_VIDEO_FORMAT_RGBA:
case GST_VIDEO_FORMAT_BGRx:
case GST_VIDEO_FORMAT_RGBx:{
const guint8 *src;
guint8 *dst;
gint srcstride, dststride;
gint i, h, w;
src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, 0);
dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, 0);
srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, 0);
dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, 0);
h = GST_VIDEO_FRAME_HEIGHT (&from_frame);
w = GST_VIDEO_FRAME_WIDTH (&from_frame) * 4;
for (i = 0; i < h; i++) {
memcpy (dst, src, w);
dst += dststride;
src += srcstride;
}
break;
}
case GST_VIDEO_FORMAT_BGR:{
const guint8 *src;
guint8 *dst;
gint srcstride, dststride;
gint i, h, w;
src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, 0);
dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, 0);
srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, 0);
dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, 0);
h = GST_VIDEO_FRAME_HEIGHT (&from_frame);
w = GST_VIDEO_FRAME_WIDTH (&from_frame) * 3;
for (i = 0; i < h; i++) {
memcpy (dst, src, w);
dst += dststride;
src += srcstride;
}
break;
}
case GST_VIDEO_FORMAT_RGB16:
case GST_VIDEO_FORMAT_RGB15:{
const guint8 *src;
guint8 *dst;
gint srcstride, dststride;
gint i, h, w;
src = GST_VIDEO_FRAME_PLANE_DATA (&from_frame, 0);
dst = GST_VIDEO_FRAME_PLANE_DATA (&to_frame, 0);
srcstride = GST_VIDEO_FRAME_PLANE_STRIDE (&from_frame, 0);
dststride = GST_VIDEO_FRAME_PLANE_STRIDE (&to_frame, 0);
h = GST_VIDEO_FRAME_HEIGHT (&from_frame);
w = GST_VIDEO_FRAME_WIDTH (&from_frame) * 2;
for (i = 0; i < h; i++) {
memcpy (dst, src, w);
dst += dststride;
src += srcstride;
}
break;
}
default:
goto unhandled_format;
}
ret = TRUE;
end:
if (from_frame.buffer)
gst_video_frame_unmap (&from_frame);
if (to_frame.buffer)
gst_video_frame_unmap (&to_frame);
UNLOCK_SINK (sink);
return ret;
unhandled_format:
GST_ERROR_OBJECT (sink,
"Unhandled format '%s' -> '%s' (should not get here)",
gst_video_format_to_string (sink->format),
d3d_format_to_string (sink->d3d.format));
ret = FALSE;
goto end;
}
static gboolean
d3d_present_swap_chain (GstD3DVideoSink * sink)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
LPDIRECT3DSURFACE9 back_buffer = NULL;
gboolean ret = FALSE;
HRESULT hr;
RECT dstr, srcr, *pDestRect = NULL, *pSrcRect = NULL;
LOCK_SINK (sink);
if (!sink->d3d.renderable || sink->d3d.device_lost) {
UNLOCK_SINK (sink);
return FALSE;
}
LOCK_CLASS (sink, klass);
CHECK_WINDOW_HANDLE (sink, end, FALSE);
CHECK_D3D_DEVICE (klass, sink, end);
CHECK_D3D_SWAPCHAIN (sink, end);
/* Set the render target to our swap chain */
IDirect3DSwapChain9_GetBackBuffer (sink->d3d.swapchain, 0,
D3DBACKBUFFER_TYPE_MONO, &back_buffer);
IDirect3DDevice9_SetRenderTarget (klass->d3d.device.d3d_device, 0,
back_buffer);
IDirect3DSurface9_Release (back_buffer);
/* Clear the target */
IDirect3DDevice9_Clear (klass->d3d.device.d3d_device, 0, NULL,
D3DCLEAR_TARGET, D3DCOLOR_XRGB (0, 0, 0), 1.0f, 0);
hr = IDirect3DDevice9_BeginScene (klass->d3d.device.d3d_device);
ERROR_CHECK_HR (hr) {
CASE_HR_ERR (D3DERR_INVALIDCALL);
CASE_HR_ERR_END (sink, "IDirect3DDevice9_BeginScene");
goto end;
}
/* Stretch and blit ops, to copy offscreen surface buffer
* to Display back buffer.
*/
d3d_stretch_and_copy (sink, back_buffer);
IDirect3DDevice9_EndScene (klass->d3d.device.d3d_device);
if (d3d_get_render_rects (sink->d3d.render_rect, &dstr, &srcr)) {
pDestRect = &dstr;
pSrcRect = &srcr;
}
/*
* Swap back and front buffers on video card and present to the user
*/
hr = IDirect3DSwapChain9_Present (sink->d3d.swapchain, pSrcRect, pDestRect,
NULL, NULL, 0);
if (hr == D3DERR_DEVICELOST) {
d3d_notify_device_lost (sink);
ret = TRUE;
goto end;
}
ERROR_CHECK_HR (hr) {
CASE_HR_ERR (D3DERR_DEVICELOST);
CASE_HR_ERR (D3DERR_DRIVERINTERNALERROR);
CASE_HR_ERR (D3DERR_INVALIDCALL);
CASE_HR_ERR (D3DERR_OUTOFVIDEOMEMORY);
CASE_HR_ERR (E_OUTOFMEMORY);
CASE_HR_DBG_END (sink, "IDirect3DSwapChain9_Present failure");
goto end;
}
ret = TRUE;
end:
UNLOCK_SINK (sink);
UNLOCK_CLASS (sink, klass);
return ret;
}
static gboolean
d3d_stretch_and_copy (GstD3DVideoSink * sink, LPDIRECT3DSURFACE9 back_buffer)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
GstVideoRectangle *render_rect = NULL;
RECT r, s;
RECT *r_p = NULL;
HRESULT hr;
gboolean ret = FALSE;
LOCK_SINK (sink);
CHECK_WINDOW_HANDLE (sink, end, FALSE);
CHECK_D3D_DEVICE (klass, sink, end);
CHECK_D3D_SURFACE (sink, end);
render_rect = sink->d3d.render_rect;
if (sink->force_aspect_ratio) {
gint window_width;
gint window_height;
GstVideoRectangle src;
GstVideoRectangle dst;
GstVideoRectangle result;
memset (&dst, 0, sizeof (dst));
memset (&src, 0, sizeof (src));
/* Set via GstXOverlay set_render_rect */
if (render_rect) {
memcpy (&dst, render_rect, sizeof (dst));
} else {
d3d_get_hwnd_window_size (sink->d3d.window_handle, &window_width,
&window_height);
dst.w = window_width;
dst.h = window_height;
}
src.w = GST_VIDEO_SINK_WIDTH (sink);
src.h = GST_VIDEO_SINK_HEIGHT (sink);
gst_video_sink_center_rect (src, dst, &result, TRUE);
r.left = result.x;
r.top = result.y;
r.right = result.x + result.w;
r.bottom = result.y + result.h;
r_p = &r;
} else if (render_rect) {
r.left = 0;
r.top = 0;
r.right = render_rect->w;
r.bottom = render_rect->h;
r_p = &r;
}
s.left = sink->crop_rect.x;
s.top = sink->crop_rect.y;
s.right = sink->crop_rect.x + sink->crop_rect.w;
s.bottom = sink->crop_rect.y + sink->crop_rect.h;
/* TODO: StretchRect returns error if the dest rect is outside
* the backbuffer area. So we need to calc how much of the src
* surface is being scaled / copied to the render rect..
*/
hr = IDirect3DDevice9_StretchRect (klass->d3d.device.d3d_device, sink->d3d.surface, /* Source Surface */
&s, /* Source Surface Rect (NULL: Whole) */
back_buffer, /* Dest Surface */
r_p, /* Dest Surface Rect (NULL: Whole) */
klass->d3d.device.filter_type);
if (hr == D3D_OK) {
ret = TRUE;
} else {
GST_ERROR_OBJECT (sink, "Failure calling Direct3DDevice9_StretchRect");
}
end:
UNLOCK_SINK (sink);
return ret;
}
GstFlowReturn
d3d_render_buffer (GstD3DVideoSink * sink, GstBuffer * buf)
{
WindowHandleVisibility handle_visibility = WINDOW_VISIBILITY_ERROR;
int clip_ret;
HDC handle_hdc;
RECT handle_rectangle;
RECT clip_rectangle;
GstFlowReturn ret = GST_FLOW_OK;
GstMemory *mem;
LPDIRECT3DSURFACE9 surface = NULL;
GstVideoCropMeta *crop = NULL;
LOCK_SINK (sink);
if (!sink->d3d.window_handle) {
if (sink->stream_stop_on_close) {
/* Handle window deletion by posting an error on the bus */
GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND,
("Output window was closed"), (NULL));
ret = GST_FLOW_ERROR;
}
goto end;
}
if (sink->d3d.device_lost) {
GST_LOG_OBJECT (sink, "Device lost, waiting for reset..");
goto end;
}
/* check for window handle visibility, if hidden skip frame rendering */
handle_hdc = GetDC (sink->d3d.window_handle);
GetClientRect (sink->d3d.window_handle, &handle_rectangle);
clip_ret = GetClipBox (handle_hdc, &clip_rectangle);
ReleaseDC (sink->d3d.window_handle, handle_hdc);
switch (clip_ret) {
case NULLREGION:
handle_visibility = WINDOW_VISIBILITY_HIDDEN;
break;
case SIMPLEREGION:
if (EqualRect (&clip_rectangle, &handle_rectangle))
handle_visibility = WINDOW_VISIBILITY_FULL;
else
handle_visibility = WINDOW_VISIBILITY_PARTIAL;
break;
case COMPLEXREGION:
handle_visibility = WINDOW_VISIBILITY_PARTIAL;
break;
default:
handle_visibility = WINDOW_VISIBILITY_ERROR;
break;
}
if (handle_visibility == WINDOW_VISIBILITY_HIDDEN) {
GST_DEBUG_OBJECT (sink, "Hidden hwnd, skipping frame rendering...");
goto end;
}
GST_INFO_OBJECT (sink, "%s %" GST_TIME_FORMAT,
(sink->d3d.window_handle != NULL) ? "Render" : "No Win",
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
crop = gst_buffer_get_video_crop_meta (buf);
if (crop) {
sink->crop_rect.x = crop->x;
sink->crop_rect.y = crop->y;
sink->crop_rect.w = crop->width;
sink->crop_rect.h = crop->height;
} else {
sink->crop_rect.x = 0;
sink->crop_rect.y = 0;
sink->crop_rect.w = sink->info.width;
sink->crop_rect.h = sink->info.height;
}
/* Resize swapchain if needed */
if (!d3d_resize_swap_chain (sink)) {
ret = GST_FLOW_ERROR;
goto end;
}
if (gst_buffer_n_memory (buf) != 1 ||
(mem = gst_buffer_peek_memory (buf, 0)) == 0 ||
!gst_memory_is_type (mem, GST_D3D_SURFACE_MEMORY_NAME)) {
GstBuffer *tmp;
GstBufferPoolAcquireParams params = { 0, };
if (!sink->fallback_pool
|| !gst_buffer_pool_set_active (sink->fallback_pool, TRUE)) {
ret = GST_FLOW_NOT_NEGOTIATED;
goto end;
}
/* take a buffer from our pool, if there is no buffer in the pool something
* is seriously wrong, waiting for the pool here might deadlock when we try
* to go to PAUSED because we never flush the pool. */
params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
ret = gst_buffer_pool_acquire_buffer (sink->fallback_pool, &tmp, &params);
if (ret != GST_FLOW_OK)
goto end;
if (sink->fallback_buffer) {
gst_buffer_unref (sink->fallback_buffer);
sink->fallback_buffer = NULL;
}
mem = gst_buffer_peek_memory (tmp, 0);
if (!mem || !gst_memory_is_type (mem, GST_D3D_SURFACE_MEMORY_NAME)) {
ret = GST_FLOW_ERROR;
gst_buffer_unref (tmp);
goto end;
}
d3d_copy_buffer (sink, buf, tmp);
buf = tmp;
surface = ((GstD3DSurfaceMemory *) mem)->surface;
/* Need to keep an additional ref until the next buffer
* to make sure it isn't reused until then */
sink->fallback_buffer = buf;
} else {
mem = gst_buffer_peek_memory (buf, 0);
surface = ((GstD3DSurfaceMemory *) mem)->surface;
if (sink->fallback_buffer) {
gst_buffer_unref (sink->fallback_buffer);
sink->fallback_buffer = NULL;
}
}
if (sink->d3d.surface)
IDirect3DSurface9_Release (sink->d3d.surface);
IDirect3DSurface9_AddRef (surface);
sink->d3d.surface = surface;
if (!d3d_present_swap_chain (sink)) {
ret = GST_FLOW_ERROR;
goto end;
}
end:
UNLOCK_SINK (sink);
return ret;
}
/** D3D Window Proc Functions **/
static LRESULT APIENTRY
d3d_wnd_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
GstD3DVideoSink *sink =
(GstD3DVideoSink *) GetProp (hWnd, TEXT ("GstD3DVideoSink"));
WNDPROC proc;
LRESULT ret = 0;
LOCK_SINK (sink);
proc = sink->d3d.orig_wnd_proc;
UNLOCK_SINK (sink);
switch (message) {
case WM_ERASEBKGND:
return TRUE;
case WM_PAINT:{
if (proc)
ret = CallWindowProc (proc, hWnd, message, wParam, lParam);
/* Call this afterwards to ensure that our paint happens last */
d3d_present_swap_chain (sink);
goto end;
}
case WM_SIZE:{
if (proc)
ret = CallWindowProc (proc, hWnd, message, wParam, lParam);
/* Don't resize if the window is being minimized. Recreating the
* swap chain will fail if the window is minimized
*/
if (wParam != SIZE_MINIMIZED)
d3d_resize_swap_chain (sink);
goto end;
}
case WM_KEYDOWN:
case WM_KEYUP:
if (sink->enable_navigation_events) {
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_KEYDOWN)
gst_navigation_send_key_event (GST_NAVIGATION (sink), "key-press",
utfrep);
else if (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:{
gdouble x = 0, y = 0;
if (sink->enable_navigation_events
&& d3d_get_render_coordinates (sink, LOWORD (lParam), HIWORD (lParam),
&x, &y)) {
gint button;
const gchar *action = NULL;
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:
break;
}
if (action) {
/* GST_DEBUG_OBJECT(sink, "%s: %lfx%lf", action, x, y); */
gst_navigation_send_mouse_event (GST_NAVIGATION (sink), action,
button, x, y);
}
}
break;
}
case WM_CLOSE:
d3d_set_window_handle (sink, 0, FALSE);
break;
default:
break;
}
if (proc)
ret = CallWindowProc (proc, hWnd, message, wParam, lParam);
else
ret = DefWindowProc (hWnd, message, wParam, lParam);
end:
return ret;
}
/** Internal Window **/
static LRESULT APIENTRY
d3d_wnd_proc_internal (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_DESTROY:
GST_DEBUG ("Internal window: WM_DESTROY");
/* Tell the internal window thread to shut down */
PostQuitMessage (0);
GST_DEBUG ("Posted quit..");
break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
static HWND
_d3d_create_internal_window (GstD3DVideoSink * sink)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
int width, height;
int offx, offy;
DWORD exstyle, style;
HWND video_window;
RECT rect;
int screenwidth;
int screenheight;
/*
* 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,
klass->d3d.wnd_class.lpszClassName,
TEXT ("GStreamer D3D video sink (internal window)"),
style, offx, offy, width, height,
NULL, NULL, klass->d3d.wnd_class.hInstance, sink);
if (video_window == NULL) {
GST_ERROR_OBJECT (sink, "Failed to create internal window: %lu",
GetLastError ());
return NULL;
}
/* Now show the window, as appropriate */
ShowWindow (video_window, SW_SHOWNORMAL);
/* Trigger the initial paint of the window */
UpdateWindow (video_window);
return video_window;
}
typedef struct
{
GstD3DVideoSink *sink;
gboolean running;
HWND hWnd;
} D3DInternalWindowDat;
static gpointer
d3d_internal_window_thread (D3DInternalWindowDat * dat)
{
GstD3DVideoSink *sink;
HWND hWnd;
MSG msg;
g_return_val_if_fail (dat != NULL, NULL);
sink = dat->sink;
GST_DEBUG_OBJECT (sink, "Entering internal window thread: %p",
g_thread_self ());
/* Create internal window */
hWnd = _d3d_create_internal_window (sink);
if (!hWnd) {
GST_ERROR_OBJECT (sink, "Failed to create internal window");
goto end;
}
dat->hWnd = hWnd;
dat->running = TRUE;
/*
* Internal window message loop
*/
while (GetMessage (&msg, NULL, 0, 0)) {
if (msg.message == WM_QUIT_THREAD)
break;
TranslateMessage (&msg);
DispatchMessage (&msg);
}
end:
GST_DEBUG_OBJECT (sink, "Exiting internal window thread: %p",
g_thread_self ());
return NULL;
}
static HWND
d3d_create_internal_window (GstD3DVideoSink * sink)
{
GThread *thread;
D3DInternalWindowDat dat;
gulong timeout_interval = 10000; /* 10 ms interval */
gulong intervals = (10000000 / timeout_interval); /* 10 secs */
gulong i;
dat.sink = sink;
dat.running = FALSE;
dat.hWnd = 0;
thread =
g_thread_new ("d3dvideosink-window-thread",
(GThreadFunc) d3d_internal_window_thread, &dat);
if (!thread) {
GST_ERROR ("Failed to created internal window thread");
return 0;
}
/* Wait 10 seconds for window proc loop to start up */
for (i = 0; dat.running == FALSE && i < intervals; i++) {
g_usleep (timeout_interval);
}
GST_DEBUG_OBJECT (sink, "Created window: %p (intervals: %lu)", dat.hWnd, i);
return dat.hWnd;
}
/*** D3D Video Class Methdos ***/
gboolean
d3d_class_init (GstD3DVideoSink * sink)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
gulong timeout_interval = 10000; /* 10 ms interval */
gulong intervals = (10000000 / timeout_interval); /* 10 secs */
gboolean ret = FALSE;
gulong i;
g_return_val_if_fail (klass != NULL, FALSE);
LOCK_CLASS (sink, klass);
klass->d3d.refs += 1;
GST_DEBUG ("D3D class init [refs:%u]", klass->d3d.refs);
klass->d3d.sink_list = g_list_append (klass->d3d.sink_list, sink);
if (klass->d3d.refs > 1)
goto end;
WM_D3DVIDEO_NOTIFY_DEVICE_LOST =
RegisterWindowMessage ("WM_D3DVIDEO_NOTIFY_DEVICE_LOST");
klass->d3d.d3d = Direct3DCreate9 (D3D_SDK_VERSION);
if (!klass->d3d.d3d) {
GST_ERROR ("Unable to create Direct3D interface");
goto error;
}
/* Register Window Class for internal Windows */
memset (&klass->d3d.wnd_class, 0, sizeof (WNDCLASS));
klass->d3d.wnd_class.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
klass->d3d.wnd_class.hInstance = GetModuleHandle (NULL);
klass->d3d.wnd_class.lpszClassName = TEXT ("GstD3DVideoSinkInternalWindow");
klass->d3d.wnd_class.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
klass->d3d.wnd_class.hCursor = LoadCursor (NULL, IDC_ARROW);
klass->d3d.wnd_class.hIcon = LoadIcon (NULL, IDI_APPLICATION);
klass->d3d.wnd_class.cbClsExtra = 0;
klass->d3d.wnd_class.cbWndExtra = 0;
klass->d3d.wnd_class.lpfnWndProc = d3d_wnd_proc_internal;
if (RegisterClass (&klass->d3d.wnd_class) == 0) {
GST_ERROR ("Failed to register window class: %lu", GetLastError ());
goto error;
}
klass->d3d.running = FALSE;
klass->d3d.error_exit = FALSE;
UNLOCK_CLASS (sink, klass);
klass->d3d.thread =
g_thread_new ("d3dvideosink-window-thread",
(GThreadFunc) d3d_hidden_window_thread, klass);
LOCK_CLASS (sink, klass);
if (!klass->d3d.thread) {
GST_ERROR ("Failed to created hidden window thread");
goto error;
}
UNLOCK_CLASS (sink, klass);
/* Wait 10 seconds for window proc loop to start up */
for (i = 0; klass->d3d.running == FALSE && i < intervals; i++) {
g_usleep (timeout_interval);
}
LOCK_CLASS (sink, klass);
if (klass->d3d.error_exit)
goto error;
if (!klass->d3d.running) {
GST_ERROR ("Waited %lu ms, window proc loop has not started",
(timeout_interval * intervals) / 1000);
goto error;
}
GST_DEBUG ("Hidden window message loop is running..");
end:
ret = TRUE;
error:
UNLOCK_CLASS (sink, klass);
if (!ret)
d3d_class_destroy (sink);
return ret;
}
void
d3d_class_destroy (GstD3DVideoSink * sink)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
g_return_if_fail (klass != NULL);
LOCK_CLASS (sink, klass);
klass->d3d.refs -= 1;
GST_DEBUG ("D3D class destroy [refs:%u]", klass->d3d.refs);
klass->d3d.sink_list = g_list_remove (klass->d3d.sink_list, sink);
if (klass->d3d.refs >= 1)
goto end;
UNLOCK_CLASS (sink, klass);
if (klass->d3d.running) {
GST_DEBUG ("Shutting down window proc thread, waiting to join..");
PostMessage (klass->d3d.hidden_window, WM_QUIT, 0, 0);
g_thread_join (klass->d3d.thread);
GST_DEBUG ("Joined..");
}
LOCK_CLASS (sink, klass);
if (klass->d3d.d3d) {
int ref_count;
ref_count = IDirect3D9_Release (klass->d3d.d3d);
GST_DEBUG ("Direct3D object released. Reference count: %d", ref_count);
}
UnregisterClass (klass->d3d.wnd_class.lpszClassName,
klass->d3d.wnd_class.hInstance);
memset (&klass->d3d, 0, sizeof (GstD3DDataClass));
end:
UNLOCK_CLASS (sink, klass);
}
static gboolean
d3d_class_display_device_create (GstD3DVideoSinkClass * klass, UINT adapter)
{
LPDIRECT3D9 d3d;
GstD3DDisplayDevice *device;
HWND hwnd;
D3DCAPS9 caps;
D3DDISPLAYMODE disp_mode;
DWORD create_mask = 0;
HRESULT hr;
gboolean ret = FALSE;
g_return_val_if_fail (klass != NULL, FALSE);
GST_DEBUG (" ");
LOCK_CLASS (NULL, klass);
d3d = klass->d3d.d3d;
device = &klass->d3d.device;
hwnd = klass->d3d.hidden_window;
memset (&caps, 0, sizeof (caps));
memset (&disp_mode, 0, sizeof (disp_mode));
memset (&device->present_params, 0, sizeof (device->present_params));
device->adapter = adapter;
if (IDirect3D9_GetAdapterDisplayMode (d3d, adapter, &disp_mode) != D3D_OK) {
GST_ERROR ("Unable to request adapter[%u] display mode", adapter);
goto error;
}
if (IDirect3D9_GetDeviceCaps (d3d, adapter, D3DDEVTYPE_HAL, &caps) != D3D_OK) {
GST_ERROR ("Unable to request adapter[%u] device caps", adapter);
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.
*/
create_mask = 0 | D3DCREATE_FPU_PRESERVE;
/* Make sure that device access is threadsafe */
create_mask |= 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 ((caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) ==
D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
create_mask |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
/* if ((d3dcaps.DevCaps & D3DDEVCAPS_PUREDEVICE) == D3DDEVCAPS_PUREDEVICE) */
/* d3dcreate |= D3DCREATE_PUREDEVICE; */
} else {
create_mask |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
/* Check the filter type. */
if ((caps.StretchRectFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) ==
D3DPTFILTERCAPS_MINFLINEAR
|| (caps.StretchRectFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR) ==
D3DPTFILTERCAPS_MAGFLINEAR) {
device->filter_type = D3DTEXF_LINEAR;
} else {
device->filter_type = D3DTEXF_NONE;
}
/* Setup the display mode format. */
device->format = disp_mode.Format;
/* present_params.Flags = D3DPRESENTFLAG_VIDEO; */
device->present_params.Windowed = TRUE;
device->present_params.SwapEffect = D3DSWAPEFFECT_DISCARD;
device->present_params.BackBufferCount = 1;
device->present_params.BackBufferFormat = device->format;
device->present_params.BackBufferWidth = 1;
device->present_params.BackBufferHeight = 1;
device->present_params.MultiSampleType = D3DMULTISAMPLE_NONE;
device->present_params.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; /* D3DPRESENT_INTERVAL_IMMEDIATE; */
GST_DEBUG ("Creating Direct3D device for hidden window %p", NULL);
if ((hr = IDirect3D9_CreateDevice (d3d, adapter, D3DDEVTYPE_HAL, hwnd,
create_mask, &device->present_params,
&device->d3d_device)) != D3D_OK) {
GST_ERROR ("Unable to create Direct3D device. Result: %ld (0x%lx)", hr, hr);
goto error;
}
GST_DEBUG ("Display Device format: %s",
d3d_format_to_string (disp_mode.Format));
ret = TRUE;
goto end;
error:
memset (device, 0, sizeof (GstD3DDisplayDevice));
end:
UNLOCK_CLASS (NULL, klass);
return ret;
}
static void
d3d_class_display_device_destroy (GstD3DVideoSinkClass * klass)
{
g_return_if_fail (klass != NULL);
LOCK_CLASS (NULL, klass);
if (klass->d3d.device.d3d_device) {
int ref_count;
ref_count = IDirect3DDevice9_Release (klass->d3d.device.d3d_device);
GST_DEBUG ("Direct3D device [adapter:%u] released. Reference count: %d",
klass->d3d.device.adapter, ref_count);
}
memset (&klass->d3d.device, 0, sizeof (GstD3DDisplayDevice));
UNLOCK_CLASS (NULL, klass);
}
static void
d3d_class_notify_device_lost (GstD3DVideoSink * sink)
{
GstD3DVideoSinkClass *klass = GST_D3DVIDEOSINK_GET_CLASS (sink);
PostMessage (klass->d3d.hidden_window, WM_D3DVIDEO_NOTIFY_DEVICE_LOST, 0, 0);
}
static void
d3d_class_notify_device_lost_all (GstD3DVideoSinkClass * klass)
{
g_return_if_fail (klass != NULL);
LOCK_CLASS (NULL, klass);
if (!klass->d3d.device_lost) {
GList *lst, *clst;
klass->d3d.device_lost = TRUE;
GST_DEBUG ("Notifying all instances of device loss");
clst = g_list_copy (klass->d3d.sink_list);
UNLOCK_CLASS (NULL, klass);
for (lst = clst; lst != NULL; lst = lst->next) {
GstD3DVideoSink *sink = (GstD3DVideoSink *) lst->data;
if (!sink)
continue;
d3d_notify_device_lost (sink);
}
g_list_free (clst);
LOCK_CLASS (NULL, klass);
/* Set timer to try reset at given interval */
SetTimer (klass->d3d.hidden_window, IDT_DEVICE_RESET_TIMER, 500, NULL);
}
UNLOCK_CLASS (NULL, klass);
}
static void
d3d_class_reset_display_device (GstD3DVideoSinkClass * klass)
{
HRESULT hr;
g_return_if_fail (klass != NULL);
LOCK_CLASS (NULL, klass);
hr = IDirect3DDevice9_Reset (klass->d3d.device.d3d_device,
&klass->d3d.device.present_params);
ERROR_CHECK_HR (hr) {
CASE_HR_ERR (D3DERR_DEVICELOST);
CASE_HR_ERR (D3DERR_DEVICEREMOVED);
CASE_HR_ERR (D3DERR_DRIVERINTERNALERROR);
CASE_HR_ERR (D3DERR_OUTOFVIDEOMEMORY);
CASE_HR_DBG_END (NULL, "Attempt device reset.. failed");
goto end;
}
GST_INFO ("Attempt device reset.. success");
klass->d3d.device_lost = FALSE;
KillTimer (klass->d3d.hidden_window, IDT_DEVICE_RESET_TIMER);
g_list_foreach (klass->d3d.sink_list, (GFunc) d3d_notify_device_reset, NULL);
end:;
UNLOCK_CLASS (NULL, klass);
}
/** Hidden Window Loop Thread **/
static LRESULT APIENTRY
D3DHiddenWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_TIMER:
switch (wParam) {
case IDT_DEVICE_RESET_TIMER:
d3d_class_reset_display_device ((GstD3DVideoSinkClass *)
GetWindowLongPtr (hWnd, GWLP_USERDATA));
break;
default:;
}
return 0;
case WM_DESTROY:
PostQuitMessage (0);
return 0;
default:
/* non constants */
if (message == WM_D3DVIDEO_NOTIFY_DEVICE_LOST) {
d3d_class_notify_device_lost_all ((GstD3DVideoSinkClass *)
GetWindowLongPtr (hWnd, GWLP_USERDATA));
return 0;
}
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
static gboolean
d3d_hidden_window_thread (GstD3DVideoSinkClass * klass)
{
WNDCLASS WndClass;
gboolean reged = FALSE;
HWND hWnd = 0;
gboolean ret = FALSE;
g_return_val_if_fail (klass != NULL, FALSE);
memset (&WndClass, 0, sizeof (WNDCLASS));
WndClass.hInstance = GetModuleHandle (NULL);
WndClass.lpszClassName = TEXT ("gstd3dvideo-hidden-window-class");
WndClass.lpfnWndProc = D3DHiddenWndProc;
if (!RegisterClass (&WndClass)) {
GST_ERROR ("Unable to register Direct3D hidden window class");
goto error;
}
reged = TRUE;
hWnd = CreateWindowEx (0,
WndClass.lpszClassName,
TEXT ("GStreamer Direct3D hidden window"),
WS_POPUP, 0, 0, 1, 1, HWND_MESSAGE, NULL, WndClass.hInstance, klass);
if (hWnd == NULL) {
GST_ERROR ("Failed to create Direct3D hidden window");
goto error;
}
GST_DEBUG ("Direct3D hidden window handle: %p", hWnd);
klass->d3d.hidden_window = hWnd;
/* TODO: Multi-monitor setup? */
if (!d3d_class_display_device_create (klass, D3DADAPTER_DEFAULT)) {
GST_ERROR ("Failed to initiazlize adapter: %u", D3DADAPTER_DEFAULT);
goto error;
}
/* Attach data to window */
SetWindowLongPtr (hWnd, GWLP_USERDATA, (LONG_PTR) klass);
GST_DEBUG ("Entering Direct3D hidden window message loop");
klass->d3d.running = TRUE;
/* Hidden Window Message Loop */
while (1) {
MSG msg;
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
if (msg.message == WM_QUIT || msg.message == WM_CLOSE)
break;
}
klass->d3d.running = FALSE;
GST_DEBUG ("Leaving Direct3D hidden window message loop");
ret = TRUE;
error:
if (!ret)
klass->d3d.error_exit = TRUE;
if (hWnd) {
PostMessage (hWnd, WM_DESTROY, 0, 0);
DestroyWindow (hWnd);
klass->d3d.hidden_window = 0;
}
if (reged)
UnregisterClass (WndClass.lpszClassName, WndClass.hInstance);
d3d_class_display_device_destroy (klass);
return ret;
}