From d43c5b6485fafea5f35ba323215a78225f434be0 Mon Sep 17 00:00:00 2001 From: Jun-ichi OKADA Date: Thu, 21 Nov 2019 13:50:21 +0900 Subject: [PATCH] winscreencap: Add dxgiscreencapsrc element. This element uses the Desktop Duplication API to capture the desktop screen at high speed. It supports Windows 8 or later. It has the following features compared to other elements: * Runs faster. * It works in High DPI environment. * Draws an accurate mouse cursor. Part-of: --- sys/winscreencap/dxgicapture.c | 1313 ++++++++++++++++++++++++++++++++ sys/winscreencap/dxgicapture.h | 83 ++ sys/winscreencap/gstdxgiscreencapsrc.c | 564 ++++++++++++++ sys/winscreencap/gstdxgiscreencapsrc.h | 32 + sys/winscreencap/gstwinscreencap.c | 15 +- sys/winscreencap/meson.build | 30 +- 6 files changed, 2033 insertions(+), 4 deletions(-) create mode 100644 sys/winscreencap/dxgicapture.c create mode 100644 sys/winscreencap/dxgicapture.h create mode 100644 sys/winscreencap/gstdxgiscreencapsrc.c create mode 100644 sys/winscreencap/gstdxgiscreencapsrc.h diff --git a/sys/winscreencap/dxgicapture.c b/sys/winscreencap/dxgicapture.c new file mode 100644 index 0000000..18c8287 --- /dev/null +++ b/sys/winscreencap/dxgicapture.c @@ -0,0 +1,1313 @@ +/* GStreamer + * Copyright (C) 2019 OKADA Jun-ichi + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* This code captures the screen using "Desktop Duplication API". + * For more information + * https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api */ + +#include "dxgicapture.h" + +#include + +#define PTR_RELEASE(p) {if(NULL!=(p)){IUnknown_Release((IUnknown *)(p)); (p) = NULL;}} +#define BYTE_PER_PIXEL (4) + +/* vertex structures */ +typedef struct _vector3d +{ + float x; + float y; + float z; +} vector3d; + +typedef struct _vector2d +{ + float x; + float y; +} vector2d; + +typedef struct _vertex +{ + vector3d pos; + vector2d texcoord; +} vertex; +#define VERTEX_NUM (6); + +typedef struct _DxgiCapture +{ + GstDXGIScreenCapSrc *src; + + /*Direct3D pointers */ + ID3D11Device *d3d11_device; + ID3D11DeviceContext *d3d11_context; + IDXGIOutputDuplication *dxgi_dupl; + + /* Texture that has been rotated and combined fragments. */ + ID3D11Texture2D *work_texture; + D3D11_TEXTURE2D_DESC work_texture_desc; + D3D11_VIEWPORT view_port; + /* Textures that can be read by the CPU. + * CPU-accessible textures are required separately from work_texture + * because shaders cannot be executed. */ + ID3D11Texture2D *readable_texture; + ID3D11VertexShader *vertex_shader; + ID3D11PixelShader *pixel_shader; + ID3D11SamplerState *sampler_state; + ID3D11RenderTargetView *target_view; + /* Screen output dimensions and rotation status. + * The texture acquired by AcquireNextFrame has a non-rotated region. */ + DXGI_OUTDUPL_DESC dupl_desc; + + /* mouse pointer image */ + guint8 *pointer_buffer; + gsize pointer_buffer_capacity; + + /* The movement rectangular regions and the movement + * destination position from the previous frame. */ + DXGI_OUTDUPL_MOVE_RECT *move_rects; + gsize move_rects_capacity; + + /* Array of dirty rectangular region for the desktop frame. */ + RECT *dirty_rects; + gsize dirty_rects_capacity; + + /* Vertex buffer created from array of dirty rectangular region. */ + vertex *dirty_verteces; + gsize verteces_capacity; + + /* Array of rectangular region to copy to readable_texture. */ + RECT *copy_rects; + gsize copy_rects_capacity; + + /* latest mouse pointer info */ + DXGI_OUTDUPL_POINTER_SHAPE_INFO pointer_shape_info; + DXGI_OUTDUPL_POINTER_POSITION last_pointer_position; + +} DxgiCapture; + +/* Vertex shader for texture rotation by HLSL. */ +static const char STR_VERTEX_SHADER[] = + "struct vs_input { float4 pos : POSITION; float2 tex : TEXCOORD; }; " + "struct vs_output { float4 pos : SV_POSITION; float2 tex : TEXCOORD; }; " + "vs_output vs_main(vs_input input){return input;}"; + +/* Pixel shader for texture rotation by HLSL. */ +static const char STR_PIXEL_SHADER[] = + "Texture2D tx : register( t0 ); " + "SamplerState samp : register( s0 ); " + "struct ps_input { float4 pos : SV_POSITION; float2 tex : TEXCOORD;}; " + "float4 ps_main(ps_input input) : " + "SV_Target{ return tx.Sample( samp, input.tex ); }"; + +/* initial buffer size */ +const int INITIAL_POINTER_BUFFER_CAPACITY = 64 * 64 * BYTE_PER_PIXEL; +const int INITIAL_MOVE_RECTS_CAPACITY = 100; +const int INITIAL_DIRTY_RECTS_CAPACITY = 100; +const int INITIAL_VERTICES_CAPACITY = 100 * VERTEX_NUM; +const int INITIAL_COPY_RECTS_CAPACITY = 100; + +static D3D_FEATURE_LEVEL feature_levels[] = { + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1, +}; + +static D3D11_INPUT_ELEMENT_DESC vertex_layout[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, + D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, + 0} +}; + +static void _draw_pointer (DxgiCapture * self, LPBYTE buffer, LPRECT dst_rect, + int stride); +static ID3D11Texture2D *_create_texture (DxgiCapture * self, + enum D3D11_USAGE usage, UINT bindFlags, UINT cpuAccessFlags); + +static gboolean _setup_texture (DxgiCapture * self); + +static HRESULT _update_work_texture (DxgiCapture * self, + IDXGIResource * desktop_resource); + +static HRESULT _copy_dirty_fragment (DxgiCapture * self, + ID3D11Texture2D * src_texture, const D3D11_TEXTURE2D_DESC * src_desc, + guint move_count, guint dirty_count, RECT ** dst_rect); + +static void _set_verteces (DxgiCapture * self, vertex * verteces, + RECT * dest_rect, const D3D11_TEXTURE2D_DESC * dst_desc, RECT * rect, + const D3D11_TEXTURE2D_DESC * src_desc); + +DxgiCapture * +dxgicap_new (HMONITOR monitor, GstDXGIScreenCapSrc * src) +{ + int i, j; + HRESULT hr; + IDXGIFactory1 *dxgi_factory1 = NULL; + IDXGIOutput1 *dxgi_output1 = NULL; + IDXGIAdapter1 *dxgi_adapter1 = NULL; + ID3D11InputLayout *vertex_input_layout = NULL; + ID3DBlob *vertex_shader_blob = NULL; + ID3DBlob *pixel_shader_blob = NULL; + D3D11_SAMPLER_DESC sampler_desc; + + DxgiCapture *self = g_new0 (DxgiCapture, 1); + if (NULL == self) { + return NULL; + } + + self->src = src; + hr = CreateDXGIFactory1 (&IID_IDXGIFactory1, (void **) &dxgi_factory1); + HR_FAILED_GOTO (hr, CreateDXGIFactory1, new_error); + + dxgi_output1 = NULL; + for (i = 0; + IDXGIFactory1_EnumAdapters1 (dxgi_factory1, i, + &dxgi_adapter1) != DXGI_ERROR_NOT_FOUND; ++i) { + IDXGIOutput *dxgi_output = NULL; + D3D_FEATURE_LEVEL feature_level; + + hr = D3D11CreateDevice ((IDXGIAdapter *) dxgi_adapter1, + D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, + feature_levels, G_N_ELEMENTS (feature_levels), + D3D11_SDK_VERSION, &self->d3d11_device, &feature_level, + &self->d3d11_context); + if (FAILED (hr)) { + HR_FAILED_INFO (hr, D3D11CreateDevice); + PTR_RELEASE (dxgi_adapter1); + continue; + } + + for (j = 0; IDXGIAdapter1_EnumOutputs (dxgi_adapter1, j, &dxgi_output) != + DXGI_ERROR_NOT_FOUND; ++j) { + DXGI_OUTPUT_DESC output_desc; + hr = IDXGIOutput_QueryInterface (dxgi_output, &IID_IDXGIOutput1, + (void **) &dxgi_output1); + PTR_RELEASE (dxgi_output); + HR_FAILED_GOTO (hr, IDXGIOutput::QueryInterface, new_error); + + hr = IDXGIOutput1_GetDesc (dxgi_output1, &output_desc); + HR_FAILED_GOTO (hr, IDXGIOutput1::GetDesc, new_error); + + if (output_desc.Monitor == monitor) { + GST_DEBUG_OBJECT (src, "found monitor"); + break; + } + + PTR_RELEASE (dxgi_output1); + dxgi_output1 = NULL; + } + + PTR_RELEASE (dxgi_adapter1); + + if (NULL != dxgi_output1) { + break; + } + + PTR_RELEASE (self->d3d11_device); + PTR_RELEASE (self->d3d11_context); + } + + if (NULL == dxgi_output1) { + goto new_error; + } + + PTR_RELEASE (dxgi_factory1); + + hr = IDXGIOutput1_DuplicateOutput (dxgi_output1, + (IUnknown *) (self->d3d11_device), &self->dxgi_dupl); + PTR_RELEASE (dxgi_output1); + HR_FAILED_GOTO (hr, IDXGIOutput1::DuplicateOutput, new_error); + + IDXGIOutputDuplication_GetDesc (self->dxgi_dupl, &self->dupl_desc); + self->pointer_buffer_capacity = INITIAL_POINTER_BUFFER_CAPACITY; + self->pointer_buffer = g_malloc (self->pointer_buffer_capacity); + if (NULL == self->pointer_buffer) { + goto new_error; + } + + self->move_rects_capacity = INITIAL_MOVE_RECTS_CAPACITY; + self->move_rects = g_new0 (DXGI_OUTDUPL_MOVE_RECT, self->move_rects_capacity); + if (NULL == self->move_rects) { + goto new_error; + } + + self->dirty_rects_capacity = INITIAL_DIRTY_RECTS_CAPACITY; + self->dirty_rects = g_new0 (RECT, self->dirty_rects_capacity); + if (NULL == self->dirty_rects) { + goto new_error; + } + + self->verteces_capacity = INITIAL_VERTICES_CAPACITY; + self->dirty_verteces = g_new0 (vertex, self->verteces_capacity); + if (NULL == self->dirty_verteces) { + goto new_error; + } + + self->copy_rects_capacity = INITIAL_COPY_RECTS_CAPACITY; + self->copy_rects = g_new0 (RECT, self->copy_rects_capacity); + if (NULL == self->copy_rects) { + goto new_error; + } + + if (DXGI_MODE_ROTATION_IDENTITY != self->dupl_desc.Rotation) { + /* For a rotated display, create a shader. */ + hr = D3DCompile (STR_VERTEX_SHADER, sizeof (STR_VERTEX_SHADER), + NULL, NULL, NULL, "vs_main", "vs_4_0_level_9_1", + 0, 0, &vertex_shader_blob, NULL); + HR_FAILED_GOTO (hr, D3DCompile, new_error); + + hr = D3DCompile (STR_PIXEL_SHADER, sizeof (STR_PIXEL_SHADER), + NULL, NULL, NULL, "ps_main", "ps_4_0_level_9_1", + 0, 0, &pixel_shader_blob, NULL); + HR_FAILED_GOTO (hr, D3DCompile, new_error); + + hr = ID3D11Device_CreateVertexShader (self->d3d11_device, + ID3D10Blob_GetBufferPointer (vertex_shader_blob), + ID3D10Blob_GetBufferSize (vertex_shader_blob), NULL, + &self->vertex_shader); + HR_FAILED_GOTO (hr, ID3D11Device::CreateVertexShader, new_error); + + hr = ID3D11Device_CreateInputLayout (self->d3d11_device, vertex_layout, + G_N_ELEMENTS (vertex_layout), + ID3D10Blob_GetBufferPointer (vertex_shader_blob), + ID3D10Blob_GetBufferSize (vertex_shader_blob), &vertex_input_layout); + PTR_RELEASE (vertex_shader_blob) + HR_FAILED_GOTO (hr, ID3D11Device::CreateInputLayout, new_error); + + ID3D11DeviceContext_IASetInputLayout (self->d3d11_context, + vertex_input_layout); + PTR_RELEASE (vertex_input_layout); + + hr = ID3D11Device_CreatePixelShader (self->d3d11_device, + ID3D10Blob_GetBufferPointer (pixel_shader_blob), + ID3D10Blob_GetBufferSize (pixel_shader_blob), NULL, + &self->pixel_shader); + PTR_RELEASE (pixel_shader_blob); + HR_FAILED_GOTO (hr, ID3D11Device::CreatePixelShader, new_error); + + memset (&sampler_desc, 0, sizeof (sampler_desc)); + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + sampler_desc.MinLOD = 0; + sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; + + hr = ID3D11Device_CreateSamplerState (self->d3d11_device, &sampler_desc, + &self->sampler_state); + HR_FAILED_GOTO (hr, ID3D11Device::CreateSamplerState, new_error); + } + + return self; + +new_error: + PTR_RELEASE (vertex_input_layout); + PTR_RELEASE (vertex_shader_blob); + PTR_RELEASE (pixel_shader_blob); + + dxgicap_destory (self); + return NULL; +} + +void +dxgicap_destory (DxgiCapture * self) +{ + if (!self) + return; + PTR_RELEASE (self->target_view); + PTR_RELEASE (self->readable_texture); + PTR_RELEASE (self->work_texture); + PTR_RELEASE (self->dxgi_dupl); + PTR_RELEASE (self->d3d11_context); + PTR_RELEASE (self->d3d11_device); + PTR_RELEASE (self->vertex_shader); + PTR_RELEASE (self->pixel_shader); + PTR_RELEASE (self->sampler_state); + + g_free (self->pointer_buffer); + g_free (self->move_rects); + g_free (self->dirty_rects); + g_free (self->dirty_verteces); + g_free (self->copy_rects); + + g_free (self); +} + +gboolean +dxgicap_start (DxgiCapture * self) +{ + return _setup_texture (self); +} + +void +dxgicap_stop (DxgiCapture * self) +{ + PTR_RELEASE (self->target_view); + PTR_RELEASE (self->readable_texture); + PTR_RELEASE (self->work_texture); +} + +gboolean +dxgicap_acquire_next_frame (DxgiCapture * self, gboolean show_cursor, + guint timeout) +{ + gboolean ret = FALSE; + HRESULT hr; + GstDXGIScreenCapSrc *src = self->src; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + IDXGIResource *desktop_resource = NULL; + + /* Get the latest desktop frames. */ + hr = IDXGIOutputDuplication_AcquireNextFrame (self->dxgi_dupl, + timeout, &frame_info, &desktop_resource); + if (hr == DXGI_ERROR_WAIT_TIMEOUT) { + /* In case of DXGI_ERROR_WAIT_TIMEOUT, + * it has not changed from the last time. */ + GST_LOG_OBJECT (src, "DXGI_ERROR_WAIT_TIMEOUT"); + ret = TRUE; + goto end; + } + HR_FAILED_GOTO (hr, IDXGIOutputDuplication::AcquireNextFrame, end); + + if (0 != frame_info.LastPresentTime.QuadPart) { + /* The desktop frame has changed since last time. */ + hr = _update_work_texture (self, desktop_resource); + if (FAILED (hr)) { + GST_DEBUG_OBJECT (src, "failed to _update_work_texture"); + goto end; + } + } + + if (show_cursor && 0 != frame_info.LastMouseUpdateTime.QuadPart) { + /* The mouse pointer has changed since last time. */ + self->last_pointer_position = frame_info.PointerPosition; + + if (0 < frame_info.PointerShapeBufferSize) { + /* A valid mouse cursor shape exists. */ + DXGI_OUTDUPL_POINTER_SHAPE_INFO pointer_shape_info; + guint pointer_shape_size_required; + /* Get the mouse cursor shape. */ + hr = IDXGIOutputDuplication_GetFramePointerShape (self->dxgi_dupl, + self->pointer_buffer_capacity, + self->pointer_buffer, + &pointer_shape_size_required, &pointer_shape_info); + if (DXGI_ERROR_MORE_DATA == hr) { + /* not enough buffers */ + self->pointer_buffer_capacity = pointer_shape_size_required * 2; + self->pointer_buffer = + g_realloc (self->pointer_buffer, self->pointer_buffer_capacity); + + hr = IDXGIOutputDuplication_GetFramePointerShape (self->dxgi_dupl, + self->pointer_buffer_capacity, + self->pointer_buffer, + &pointer_shape_size_required, &pointer_shape_info); + } + HR_FAILED_GOTO (hr, IDXGIOutputDuplication::GetFramePointerShape, end); + self->pointer_shape_info = pointer_shape_info; + ret = TRUE; + } else { + ret = TRUE; + } + } else { + ret = TRUE; + } +end: + IDXGIOutputDuplication_ReleaseFrame (self->dxgi_dupl); + PTR_RELEASE (desktop_resource); + return ret; +} + +gboolean +dxgicap_copy_buffer (DxgiCapture * self, gboolean show_cursor, LPRECT dst_rect, + GstVideoInfo * video_info, GstBuffer * buf) +{ + HRESULT hr; + int i; + GstDXGIScreenCapSrc *src = self->src; + D3D11_MAPPED_SUBRESOURCE readable_map; + GstVideoFrame vframe; + gint height = RECT_HEIGHT ((*dst_rect)); + gint width = RECT_WIDTH ((*dst_rect)); + + if (NULL == self->readable_texture) { + GST_DEBUG_OBJECT (src, "readable_texture is null"); + goto flow_error; + } + + hr = ID3D11DeviceContext_Map (self->d3d11_context, + (ID3D11Resource *) self->readable_texture, 0, + D3D11_MAP_READ, 0, &readable_map); + HR_FAILED_GOTO (hr, IDXGISurface1::Map, flow_error); + GST_DEBUG_OBJECT (src, "copy size width:%d height:%d", width, height); + + /* Copy from readable_texture to GstVideFrame. */ + if (gst_video_frame_map (&vframe, video_info, buf, GST_MAP_WRITE)) { + gint line_size; + gint stride_dst; + PBYTE frame_buffer; + PBYTE p_dst; + PBYTE p_src; + + frame_buffer = GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0); + p_src = (PBYTE) readable_map.pData + + (dst_rect->top * readable_map.RowPitch) + + (dst_rect->left * BYTE_PER_PIXEL); + p_dst = frame_buffer; + + line_size = width * BYTE_PER_PIXEL; + stride_dst = GST_VIDEO_FRAME_PLANE_STRIDE (&vframe, 0); + + if (line_size > stride_dst) { + GST_ERROR_OBJECT (src, "not enough stride in video frame"); + ID3D11DeviceContext_Unmap (self->d3d11_context, + (ID3D11Resource *) self->readable_texture, 0); + gst_video_frame_unmap (&vframe); + goto flow_error; + } + + for (i = 0; i < height; ++i) { + memcpy (p_dst, p_src, line_size); + p_dst += stride_dst; + p_src += readable_map.RowPitch; + } + ID3D11DeviceContext_Unmap (self->d3d11_context, + (ID3D11Resource *) self->readable_texture, 0); + HR_FAILED_GOTO (hr, IDXGISurface1::Unmap, flow_error); + + if (show_cursor && self->last_pointer_position.Visible) { + _draw_pointer (self, frame_buffer, dst_rect, stride_dst); + } + gst_video_frame_unmap (&vframe); + return TRUE; + } + +flow_error: + return FALSE; +} + +static void +_draw_pointer (DxgiCapture * self, PBYTE buffer, LPRECT dst_rect, int stride) +{ + RECT pointer_rect; + RECT clip_pointer_rect; + int offset_x; + int offset_y; + PBYTE p_dst; + /* For DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME, halve the height. */ + int pointer_height = + (DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME == + self->pointer_shape_info.Type) + ? self->pointer_shape_info.Height / 2 : self->pointer_shape_info.Height; + + /* A rectangular area containing the mouse pointer shape */ + SetRect (&pointer_rect, + self->last_pointer_position.Position.x, + self->last_pointer_position.Position.y, + self->last_pointer_position.Position.x + + self->pointer_shape_info.Width, + self->last_pointer_position.Position.y + pointer_height); + + if (!IntersectRect (&clip_pointer_rect, dst_rect, &pointer_rect)) { + return; + } + + /* Draw a pointer if it overlaps the destination rectangle range. + * There are three ways to draw the mouse cursor. + * see https://docs.microsoft.com/ja-jp/windows/win32/api/dxgi1_2/ne-dxgi1_2-dxgi_outdupl_pointer_shape_type */ + offset_x = clip_pointer_rect.left - pointer_rect.left; + offset_y = clip_pointer_rect.top - pointer_rect.top; + p_dst = + ((PBYTE) buffer) + ((clip_pointer_rect.top - + dst_rect->top) * stride) + + ((clip_pointer_rect.left - dst_rect->left) * BYTE_PER_PIXEL); + + if (DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR == + self->pointer_shape_info.Type + || DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR == + self->pointer_shape_info.Type) { + gboolean mask_mode = + DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR == + self->pointer_shape_info.Type; + PBYTE p_src = + (PBYTE) self->pointer_buffer + + (offset_y * self->pointer_shape_info.Pitch) + + (offset_x * BYTE_PER_PIXEL); + + int y, x; + for (y = 0; y < RECT_HEIGHT (clip_pointer_rect); ++y) { + for (x = 0; x < RECT_WIDTH (clip_pointer_rect); ++x) { + PBYTE p1 = p_dst + (x * BYTE_PER_PIXEL); + PBYTE p2 = p_src + (x * BYTE_PER_PIXEL); + int alpha = *(p2 + 3); + int i; + for (i = 0; i < 3; ++i) { + if (mask_mode) { + /* case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + * If the alpha channel of a pixel in the mouse image is 0, copy it. + * Otherwise, xor each pixel. */ + if (0 == alpha) { + *p1 = *p2; + } else { + *p1 = *p2 ^ *p1; + } + } else { + /* case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + * Copies the mouse cursor image with alpha channel composition. */ + *p1 = min (255, max (0, *p1 + ((*p2 - *p1) * alpha / 255))); + } + ++p1; + ++p2; + } + } + p_dst += stride; + p_src += self->pointer_shape_info.Pitch; + } + } else if (DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME == + self->pointer_shape_info.Type) { + guint mask_bit = 0x80; + /* AND MASK pointer + * It is stored in 1 bit per pixel from the beginning. */ + PBYTE p_src_and = + (PBYTE) self->pointer_buffer + + (offset_y * self->pointer_shape_info.Pitch); + /* XOR MASK pointer + * The XOR MASK is stored after the AND mask. */ + PBYTE p_src_xor = + (PBYTE) self->pointer_buffer + + ((offset_y + pointer_height) * self->pointer_shape_info.Pitch); + + int y, x; + for (y = 0; y < RECT_HEIGHT (clip_pointer_rect); ++y) { + guint32 *p_dst_32 = ((guint32 *) (p_dst)); + for (x = offset_x; x < RECT_WIDTH (clip_pointer_rect); ++x) { + int bit_pos = x % 8; + gboolean and_bit = + 0 != (*(p_src_and + (x / 8)) & (mask_bit >> bit_pos)); + gboolean xor_bit = + 0 != (*(p_src_xor + (x / 8)) & (mask_bit >> bit_pos)); + + if (and_bit) { + if (xor_bit) { + *p_dst_32 = *p_dst_32 ^ 0x00ffffff; + } + } else { + if (xor_bit) { + *p_dst_32 = 0xffffffff; + } else { + *p_dst_32 = 0xff000000; + } + } + ++p_dst_32; + } + p_dst += stride; + p_src_and += self->pointer_shape_info.Pitch; + p_src_xor += self->pointer_shape_info.Pitch; + } + } +} + +static ID3D11Texture2D * +_create_texture (DxgiCapture * self, + enum D3D11_USAGE usage, UINT bindFlags, UINT cpuAccessFlags) +{ + HRESULT hr; + GstDXGIScreenCapSrc *src = self->src; + D3D11_TEXTURE2D_DESC new_desc; + ID3D11Texture2D *new_texture = NULL; + + ZeroMemory (&new_desc, sizeof (new_desc)); + new_desc.Width = self->dupl_desc.ModeDesc.Width; + new_desc.Height = self->dupl_desc.ModeDesc.Height; + new_desc.MipLevels = 1; + new_desc.ArraySize = 1; + new_desc.SampleDesc.Count = 1; + new_desc.SampleDesc.Quality = 0; + new_desc.Usage = usage; + new_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + new_desc.BindFlags = bindFlags; + new_desc.CPUAccessFlags = cpuAccessFlags; + new_desc.MiscFlags = 0; + + hr = ID3D11Device_CreateTexture2D (self->d3d11_device, &new_desc, NULL, + &new_texture); + HR_FAILED_RET (hr, ID3D11Device::CreateTexture2D, NULL); + + return new_texture; +} + +static gboolean +_setup_texture (DxgiCapture * self) +{ + HRESULT hr; + ID3D11Texture2D *new_texture = NULL; + GstDXGIScreenCapSrc *src = self->src; + + if (NULL == self->readable_texture) { + new_texture = _create_texture (self, D3D11_USAGE_STAGING, 0, + D3D11_CPU_ACCESS_READ); + if (NULL == new_texture) { + return FALSE; + } + self->readable_texture = new_texture; + } + + if (DXGI_MODE_ROTATION_IDENTITY != self->dupl_desc.Rotation) { + /* For rotated displays, create work_texture. */ + if (NULL == self->work_texture) { + new_texture = + _create_texture (self, D3D11_USAGE_DEFAULT, + D3D11_BIND_RENDER_TARGET, 0); + if (NULL == new_texture) { + return FALSE; + } + + self->work_texture = new_texture; + ID3D11Texture2D_GetDesc (self->work_texture, &self->work_texture_desc); + hr = ID3D11Device_CreateRenderTargetView (self->d3d11_device, + (ID3D11Resource *) self->work_texture, NULL, &self->target_view); + HR_FAILED_RET (hr, ID3D11Device::CreateRenderTargetView, FALSE); + + self->view_port.Width = (float) self->work_texture_desc.Width; + self->view_port.Height = (float) self->work_texture_desc.Height; + self->view_port.MinDepth = 0.0f; + self->view_port.MaxDepth = 1.0f; + self->view_port.TopLeftX = 0.0f; + self->view_port.TopLeftY = 0.0f; + } + } + + return TRUE; +} + +/* Update work_texture to the latest desktop frame from the update information + * that can be obtained from IDXGIOutputDuplication. + * Then copy to readable_texture. + */ +static HRESULT +_update_work_texture (DxgiCapture * self, IDXGIResource * desktop_resource) +{ + HRESULT hr = S_OK; + GstDXGIScreenCapSrc *src = self->src; + int i; + ID3D11Texture2D *desktop_texture = NULL; + guint required_size; + guint move_count; + guint dirty_rects_capacity_size; + guint dirty_count; + guint copy_count; + D3D11_TEXTURE2D_DESC src_desc; + RECT *dst_rect; + ID3D11Texture2D *work_src; + guint move_rects_capacity_size = + sizeof (DXGI_OUTDUPL_MOVE_RECT) * self->move_rects_capacity; + + hr = IDXGIResource_QueryInterface (desktop_resource, &IID_ID3D11Texture2D, + (void **) &desktop_texture); + HR_FAILED_GOTO (hr, IDXGIResource::QueryInterface, end); + + /* Get the rectangular regions that was moved from the last time. + * However, I have never obtained a valid value in GetFrameMoveRects. + * It seems to depend on the implementation of the GPU driver. + * see https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgioutputduplication-getframemoverects + */ + hr = IDXGIOutputDuplication_GetFrameMoveRects (self->dxgi_dupl, + move_rects_capacity_size, self->move_rects, &required_size); + if (DXGI_ERROR_MORE_DATA == hr) { + /* not enough buffers */ + self->move_rects_capacity = + (required_size / sizeof (DXGI_OUTDUPL_MOVE_RECT)) * 2; + self->move_rects = + g_renew (DXGI_OUTDUPL_MOVE_RECT, self->move_rects, + self->move_rects_capacity); + + hr = IDXGIOutputDuplication_GetFrameMoveRects (self->dxgi_dupl, + required_size, self->move_rects, &required_size); + } + HR_FAILED_GOTO (hr, IDXGIOutputDuplication::GetFrameMoveRects, end); + move_count = required_size / sizeof (DXGI_OUTDUPL_MOVE_RECT); + + dirty_rects_capacity_size = sizeof (RECT) * self->dirty_rects_capacity; + /* Gets the rectangular regions that has changed since the last time. + see https://docs.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgioutputduplication-getframedirtyrects + */ + hr = IDXGIOutputDuplication_GetFrameDirtyRects (self->dxgi_dupl, + dirty_rects_capacity_size, self->dirty_rects, &required_size); + + if (DXGI_ERROR_MORE_DATA == hr) { + /* not enough buffers */ + self->dirty_rects_capacity = (required_size / sizeof (RECT)) * 2; + self->dirty_rects = + g_renew (RECT, self->dirty_rects, self->dirty_rects_capacity); + + hr = IDXGIOutputDuplication_GetFrameDirtyRects (self->dxgi_dupl, + required_size, self->dirty_rects, &required_size); + } + HR_FAILED_GOTO (hr, IDXGIOutputDuplication::GetFrameDirtyRects, end); + + dirty_count = required_size / sizeof (RECT); + + /* The number of rectangular regions to copy to the readable_texture. */ + copy_count = move_count + dirty_count; + + if (self->copy_rects_capacity < copy_count) { + /* not enough buffers */ + self->copy_rects_capacity = copy_count * 2; + self->copy_rects = + g_renew (RECT, self->copy_rects, self->copy_rects_capacity); + } + + if (DXGI_MODE_ROTATION_IDENTITY == self->dupl_desc.Rotation) { + /* For a non-rotating display, copy it directly into readable_texture. */ + RECT *p = self->copy_rects; + for (i = 0; i < move_count; ++i) { + *p = self->move_rects[i].DestinationRect; + ++p; + } + for (i = 0; i < dirty_count; ++i) { + *p = self->dirty_rects[i]; + ++p; + } + work_src = desktop_texture; + } else { + /* For rotated displays, rotate to work_texture and copy. */ + ID3D11Texture2D_GetDesc (desktop_texture, &src_desc); + dst_rect = self->copy_rects; + /* Copy the dirty rectangular and moved rectangular regions from desktop frame to work_texture. */ + hr = _copy_dirty_fragment (self, desktop_texture, &src_desc, move_count, + dirty_count, &dst_rect); + work_src = self->work_texture; + if (FAILED (hr)) { + goto end; + } + } + + /* Copy the updated rectangular regions to readable_texture. */ + for (i = 0; i < copy_count; ++i) { + RECT *p = (self->copy_rects + i); + D3D11_BOX box; + box.left = p->left; + box.top = p->top; + box.front = 0; + box.right = p->right; + box.bottom = p->bottom; + box.back = 1; + + ID3D11DeviceContext_CopySubresourceRegion (self->d3d11_context, + (ID3D11Resource *) self->readable_texture, + 0, p->left, p->top, 0, (ID3D11Resource *) work_src, 0, &box); + } + +end: + PTR_RELEASE (desktop_texture); + return hr; +} + +static void +_rotate_rect (DXGI_MODE_ROTATION rotation, RECT * dst, const RECT * src, + gint dst_width, gint dst_height) +{ + switch (rotation) { + case DXGI_MODE_ROTATION_ROTATE90: + dst->left = dst_width - src->bottom; + dst->top = src->left; + dst->right = dst_width - src->top; + dst->bottom = src->right; + break; + case DXGI_MODE_ROTATION_ROTATE180: + dst->left = dst_width - src->right; + dst->top = dst_height - src->bottom; + dst->right = dst_width - src->left; + dst->bottom = dst_height - src->top; + break; + case DXGI_MODE_ROTATION_ROTATE270: + dst->left = src->top; + dst->top = dst_height - src->right; + dst->right = src->bottom; + dst->bottom = dst_height - src->left; + break; + default: + *dst = *src; + break; + } +} + +/* Copy the rectangular area specified by dirty_rects and move_rects from src_texture to work_texture. */ +static HRESULT +_copy_dirty_fragment (DxgiCapture * self, ID3D11Texture2D * src_texture, + const D3D11_TEXTURE2D_DESC * src_desc, guint move_count, guint dirty_count, + RECT ** dst_rect) +{ + HRESULT hr = S_OK; + GstDXGIScreenCapSrc *src = self->src; + int i; + RECT *dst_rect_p; + vertex *vp; + UINT stride; + UINT offset; + guint verteces_count; + ID3D11Buffer *verteces_buffer = NULL; + ID3D11ShaderResourceView *shader_resource = NULL; + D3D11_SUBRESOURCE_DATA subresource_data; + D3D11_BUFFER_DESC buffer_desc; + D3D11_SHADER_RESOURCE_VIEW_DESC shader_desc; + + shader_desc.Format = src_desc->Format; + shader_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + shader_desc.Texture2D.MostDetailedMip = src_desc->MipLevels - 1; + shader_desc.Texture2D.MipLevels = src_desc->MipLevels; + hr = ID3D11Device_CreateShaderResourceView (self->d3d11_device, + (ID3D11Resource *) src_texture, &shader_desc, &shader_resource); + HR_FAILED_GOTO (hr, ID3D11Device::CreateShaderResourceView, end); + + ID3D11DeviceContext_OMSetRenderTargets (self->d3d11_context, 1, + &self->target_view, NULL); + + ID3D11DeviceContext_VSSetShader (self->d3d11_context, self->vertex_shader, + NULL, 0); + + ID3D11DeviceContext_PSSetShader (self->d3d11_context, self->pixel_shader, + NULL, 0); + + ID3D11DeviceContext_PSSetShaderResources (self->d3d11_context, 0, 1, + &shader_resource); + + ID3D11DeviceContext_PSSetSamplers (self->d3d11_context, 0, 1, + &self->sampler_state); + + ID3D11DeviceContext_IASetPrimitiveTopology (self->d3d11_context, + D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + verteces_count = (move_count + dirty_count) * VERTEX_NUM; + if (verteces_count > self->verteces_capacity) { + /* not enough buffers */ + self->verteces_capacity = verteces_count * 2; + self->dirty_verteces = + g_renew (vertex, self->dirty_verteces, self->verteces_capacity); + if (NULL == self->dirty_verteces) { + hr = S_FALSE; + goto end; + } + } + + dst_rect_p = *dst_rect; + vp = self->dirty_verteces; + /* Create a vertex buffer to move and rotate from the move_rects. + * And set the rectangular region to be copied to readable_texture. */ + for (i = 0; i < move_count; ++i) { + /* Copy the area to be moved. + * The source of the move is included in dirty_rects. */ + _set_verteces (self, vp, dst_rect_p, &self->work_texture_desc, + &(self->move_rects[i].DestinationRect), src_desc); + vp += VERTEX_NUM; + ++dst_rect_p; + } + /* Create a vertex buffer to move and rotate from the dirty_rects. + * And set the rectangular region to be copied to readable_texture. */ + for (i = 0; i < dirty_count; ++i) { + _set_verteces (self, vp, dst_rect_p, &self->work_texture_desc, + &(self->dirty_rects[i]), src_desc); + vp += VERTEX_NUM; + ++dst_rect_p; + } + *dst_rect = dst_rect_p; + + memset (&buffer_desc, 0, sizeof (buffer_desc)); + buffer_desc.Usage = D3D11_USAGE_IMMUTABLE; + buffer_desc.ByteWidth = verteces_count * sizeof (vertex); + buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + buffer_desc.CPUAccessFlags = 0; + + memset (&subresource_data, 0, sizeof (subresource_data)); + subresource_data.pSysMem = self->dirty_verteces; + + hr = ID3D11Device_CreateBuffer (self->d3d11_device, &buffer_desc, + &subresource_data, &verteces_buffer); + HR_FAILED_GOTO (hr, ID3D11Device::CreateBuffer, end); + + stride = sizeof (vertex); + offset = 0; + ID3D11DeviceContext_IASetVertexBuffers (self->d3d11_context, 0, 1, + &verteces_buffer, &stride, &offset); + + ID3D11DeviceContext_RSSetViewports (self->d3d11_context, 1, &self->view_port); + + /* Copy the rectangular region indicated by dirty_rects from the desktop frame to work_texture. */ + ID3D11DeviceContext_Draw (self->d3d11_context, verteces_count, 0); + +end: + PTR_RELEASE (verteces_buffer); + PTR_RELEASE (shader_resource); + + return hr; +} + +static void +_set_verteces (DxgiCapture * self, vertex * verteces, RECT * dst_rect, + const D3D11_TEXTURE2D_DESC * dst_desc, RECT * rect, + const D3D11_TEXTURE2D_DESC * src_desc) +{ + int center_x; + int center_y; + + /* Rectangular area is moved according to the rotation of the display. */ + _rotate_rect (self->dupl_desc.Rotation, dst_rect, rect, dst_desc->Width, + dst_desc->Height); + + /* Set the vertex buffer from the rotation of the display. */ + switch (self->dupl_desc.Rotation) { + case DXGI_MODE_ROTATION_ROTATE90: + verteces[0].texcoord = (vector2d) { + (float) rect->right / (float) src_desc->Width, + (float) rect->bottom / (float) src_desc->Height}; + verteces[1].texcoord = (vector2d) { + (float) rect->left / (float) src_desc->Width, + (float) rect->bottom / (float) src_desc->Height}; + verteces[2].texcoord = (vector2d) { + (float) rect->right / (float) src_desc->Width, + (float) rect->top / (float) src_desc->Height}; + verteces[5].texcoord = (vector2d) { + (float) rect->left / (float) src_desc->Width, + (float) rect->top / (float) src_desc->Height}; + break; + case DXGI_MODE_ROTATION_ROTATE180: + verteces[0].texcoord = (vector2d) { + (float) rect->right / (float) src_desc->Width, + (float) rect->top / (float) src_desc->Height}; + verteces[1].texcoord = (vector2d) { + (float) rect->right / (float) src_desc->Width, + (float) rect->bottom / (float) src_desc->Height}; + verteces[2].texcoord = (vector2d) { + (float) rect->left / (float) src_desc->Width, + (float) rect->top / (float) src_desc->Height}; + verteces[5].texcoord = (vector2d) { + (float) rect->left / (float) src_desc->Width, + (float) rect->bottom / (float) src_desc->Height}; + break; + case DXGI_MODE_ROTATION_ROTATE270: + verteces[0].texcoord = (vector2d) { + (float) rect->left / (float) src_desc->Width, + (float) rect->top / (float) src_desc->Height}; + verteces[1].texcoord = (vector2d) { + (float) rect->right / (float) src_desc->Width, + (float) rect->top / (float) src_desc->Height}; + verteces[2].texcoord = (vector2d) { + (float) rect->left / (float) src_desc->Width, + (float) rect->bottom / (float) src_desc->Height}; + verteces[5].texcoord = (vector2d) { + (float) rect->right / (float) src_desc->Width, + (float) rect->bottom / (float) src_desc->Height}; + break; + default: + verteces[0].texcoord = (vector2d) { + (float) rect->left / (float) src_desc->Width, + (float) rect->bottom / (float) src_desc->Height}; + verteces[1].texcoord = (vector2d) { + (float) rect->left / (float) src_desc->Width, + (float) rect->top / (float) src_desc->Height}; + verteces[2].texcoord = (vector2d) { + (float) rect->right / (float) src_desc->Width, + (float) rect->bottom / (float) src_desc->Height}; + verteces[5].texcoord = (vector2d) { + (float) rect->right / (float) src_desc->Width, + (float) rect->top / (float) src_desc->Height}; + break; + } + verteces[3].texcoord = verteces[2].texcoord; + verteces[4].texcoord = verteces[1].texcoord; + + center_x = (int) dst_desc->Width / 2; + center_y = (int) dst_desc->Height / 2; + + verteces[0].pos = (vector3d) { + (float) (dst_rect->left - center_x) / (float) center_x, + (float) (dst_rect->bottom - center_y) / (float) center_y *-1.0f, 0.0f}; + verteces[1].pos = (vector3d) { + (float) (dst_rect->left - center_x) / (float) center_x, + (float) (dst_rect->top - center_y) / (float) center_y *-1.0f, 0.0f}; + verteces[2].pos = (vector3d) { + (float) (dst_rect->right - center_x) / (float) center_x, + (float) (dst_rect->bottom - center_y) / (float) center_y *-1.0f, 0.0f}; + verteces[3].pos = verteces[2].pos; + verteces[4].pos = verteces[1].pos; + verteces[5].pos = (vector3d) { + (float) (dst_rect->right - center_x) / (float) center_x, + (float) (dst_rect->top - center_y) / (float) center_y *-1.0f, 0.0f}; +} + +typedef struct _monitor_param_by_name +{ + const gchar *device_name; + HMONITOR hmonitor; +} monitor_param_by_name; + +static BOOL CALLBACK +monitor_enum_proc_by_name (HMONITOR hmonitor, HDC hdc, LPRECT rect, + LPARAM lparam) +{ + MONITORINFOEXA monitor_info; + monitor_param_by_name *param = (monitor_param_by_name *) lparam; + + monitor_info.cbSize = sizeof (monitor_info); + if (GetMonitorInfoA (hmonitor, (MONITORINFO *) & monitor_info)) { + if (0 == g_strcmp0 (monitor_info.szDevice, param->device_name)) { + param->hmonitor = hmonitor; + return FALSE; + } + } + return TRUE; +} + +HMONITOR +get_hmonitor_by_device_name (const gchar * device_name) +{ + monitor_param_by_name monitor = { device_name, NULL, }; + EnumDisplayMonitors (NULL, NULL, monitor_enum_proc_by_name, + (LPARAM) & monitor); + return monitor.hmonitor; +} + +static BOOL CALLBACK +monitor_enum_proc_primary (HMONITOR hmonitor, HDC hdc, LPRECT rect, + LPARAM lparam) +{ + MONITORINFOEXA monitor_info; + monitor_param_by_name *param = (monitor_param_by_name *) lparam; + + monitor_info.cbSize = sizeof (monitor_info); + if (GetMonitorInfoA (hmonitor, (MONITORINFO *) & monitor_info)) { + if (MONITORINFOF_PRIMARY == monitor_info.dwFlags) { + param->hmonitor = hmonitor; + return FALSE; + } + } + return TRUE; +} + +HMONITOR +get_hmonitor_primary (void) +{ + monitor_param_by_name monitor = { NULL, NULL, }; + EnumDisplayMonitors (NULL, NULL, monitor_enum_proc_primary, + (LPARAM) & monitor); + return monitor.hmonitor; +} + +typedef struct _monitor_param_by_index +{ + int target; + int counter; + HMONITOR hmonitor; +} monitor_param_by_index; + +static BOOL CALLBACK +monitor_enum_proc_by_index (HMONITOR hmonitor, HDC hdc, LPRECT rect, + LPARAM lparam) +{ + MONITORINFOEXA monitor_info; + monitor_param_by_index *param = (monitor_param_by_index *) lparam; + + monitor_info.cbSize = sizeof (monitor_info); + if (GetMonitorInfoA (hmonitor, (MONITORINFO *) & monitor_info)) { + if (param->target == param->counter) { + param->hmonitor = hmonitor; + return FALSE; + } + } + ++param->counter; + return TRUE; +} + +HMONITOR +get_hmonitor_by_index (int index) +{ + monitor_param_by_index monitor = { index, 0, NULL, }; + EnumDisplayMonitors (NULL, NULL, monitor_enum_proc_by_index, + (LPARAM) & monitor); + return monitor.hmonitor; +} + + +gboolean +get_monitor_physical_size (HMONITOR hmonitor, LPRECT rect) +{ + MONITORINFOEXW monitor_info; + DEVMODEW dev_mode; + + monitor_info.cbSize = sizeof (monitor_info); + if (!GetMonitorInfoW (hmonitor, (LPMONITORINFO) & monitor_info)) { + return FALSE; + } + + dev_mode.dmSize = sizeof (dev_mode); + dev_mode.dmDriverExtra = sizeof (POINTL); + dev_mode.dmFields = DM_POSITION; + if (!EnumDisplaySettingsW + (monitor_info.szDevice, ENUM_CURRENT_SETTINGS, &dev_mode)) { + return FALSE; + } + + SetRect (rect, 0, 0, dev_mode.dmPelsWidth, dev_mode.dmPelsHeight); + return TRUE; +} + +static const gchar * +_hresult_to_string_fallback (HRESULT hr) +{ + const gchar *s = "unknown error"; + + switch (hr) { + case DXGI_ERROR_ACCESS_DENIED: + s = "DXGI_ERROR_ACCESS_DENIED"; + break; + case DXGI_ERROR_ACCESS_LOST: + s = "DXGI_ERROR_ACCESS_LOST"; + break; + case DXGI_ERROR_CANNOT_PROTECT_CONTENT: + s = "DXGI_ERROR_CANNOT_PROTECT_CONTENT"; + break; + case DXGI_ERROR_DEVICE_HUNG: + s = "DXGI_ERROR_DEVICE_HUNG"; + break; + case DXGI_ERROR_DEVICE_REMOVED: + s = "DXGI_ERROR_DEVICE_REMOVED"; + break; + case DXGI_ERROR_DEVICE_RESET: + s = "DXGI_ERROR_DEVICE_RESET"; + break; + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: + s = "DXGI_ERROR_DRIVER_INTERNAL_ERROR"; + break; + case DXGI_ERROR_FRAME_STATISTICS_DISJOINT: + s = "DXGI_ERROR_FRAME_STATISTICS_DISJOINT"; + break; + case DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE: + s = "DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE"; + break; + case DXGI_ERROR_INVALID_CALL: + s = "DXGI_ERROR_INVALID_CALL"; + break; + case DXGI_ERROR_MORE_DATA: + s = "DXGI_ERROR_MORE_DATA"; + break; + case DXGI_ERROR_NAME_ALREADY_EXISTS: + s = "DXGI_ERROR_NAME_ALREADY_EXISTS"; + break; + case DXGI_ERROR_NONEXCLUSIVE: + s = "DXGI_ERROR_NONEXCLUSIVE"; + break; + case DXGI_ERROR_NOT_CURRENTLY_AVAILABLE: + s = "DXGI_ERROR_NOT_CURRENTLY_AVAILABLE"; + break; + case DXGI_ERROR_NOT_FOUND: + s = "DXGI_ERROR_NOT_FOUND"; + break; + case DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED: + s = "DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED"; + break; + case DXGI_ERROR_REMOTE_OUTOFMEMORY: + s = "DXGI_ERROR_REMOTE_OUTOFMEMORY"; + break; + case DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE: + s = "DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE"; + break; + case DXGI_ERROR_SDK_COMPONENT_MISSING: + s = "DXGI_ERROR_SDK_COMPONENT_MISSING"; + break; + case DXGI_ERROR_SESSION_DISCONNECTED: + s = "DXGI_ERROR_SESSION_DISCONNECTED"; + break; + case DXGI_ERROR_UNSUPPORTED: + s = "DXGI_ERROR_UNSUPPORTED"; + break; + case DXGI_ERROR_WAIT_TIMEOUT: + s = "DXGI_ERROR_WAIT_TIMEOUT"; + break; + case DXGI_ERROR_WAS_STILL_DRAWING: + s = "DXGI_ERROR_WAS_STILL_DRAWING"; + break; + case E_FAIL: + s = "E_FAIL"; + break; + case E_OUTOFMEMORY: + s = "E_OUTOFMEMORY"; + break; + case E_NOTIMPL: + s = "E_NOTIMPL"; + break; + case E_ACCESSDENIED: + s = "E_ACCESSDENIED"; + break; + case E_POINTER: + s = "E_POINTER"; + break; + case E_INVALIDARG: + s = "E_INVALIDARG"; + break; +#if defined(_MSC_VER) && (_MSC_VER >= 1800) + case DXGI_ERROR_ALREADY_EXISTS: + s = "DXGI_ERROR_ALREADY_EXISTS"; + break; + case D3D11_ERROR_FILE_NOT_FOUND: + s = "D3D11_ERROR_FILE_NOT_FOUND"; + break; + case D3D11_ERROR_TOO_MANY_UNIQUE_STATE_OBJECTS: + s = "D3D11_ERROR_TOO_MANY_UNIQUE_STATE_OBJECTS"; + break; + case D3D11_ERROR_TOO_MANY_UNIQUE_VIEW_OBJECTS: + s = "D3D11_ERROR_TOO_MANY_UNIQUE_VIEW_OBJECTS"; + break; + case D3D11_ERROR_DEFERRED_CONTEXT_MAP_WITHOUT_INITIAL_DISCARD: + s = "D3D11_ERROR_DEFERRED_CONTEXT_MAP_WITHOUT_INITIAL_DISCARD"; + break; +#endif + } + return s; +} + +gchar * +get_hresult_to_string (HRESULT hr) +{ + DWORD flags; + gchar *ret_text; + LPTSTR error_text = NULL; + + flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_IGNORE_INSERTS; + FormatMessage (flags, NULL, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) & error_text, 0, NULL); + + /* If we couldn't get the error msg, try the fallback switch statement */ + if (error_text == NULL) + return g_strdup (_hresult_to_string_fallback (hr)); + +#ifdef UNICODE + /* If UNICODE is defined, LPTSTR is LPWSTR which is UTF-16 */ + ret_text = g_utf16_to_utf8 (error_text, 0, NULL, NULL, NULL); +#else + ret_text = g_strdup (error_text); +#endif + + LocalFree (error_text); + return ret_text; +} diff --git a/sys/winscreencap/dxgicapture.h b/sys/winscreencap/dxgicapture.h new file mode 100644 index 0000000..2f6c520 --- /dev/null +++ b/sys/winscreencap/dxgicapture.h @@ -0,0 +1,83 @@ +/* GStreamer + * Copyright (C) 2019 OKADA Jun-ichi + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __DXGICAP_H__ +#define __DXGICAP_H__ + +#define COBJMACROS +#include +#include +#include +#undef COBJMACROS + +#include +#include +#include + +#define RECT_WIDTH(r) (r.right - r.left) +#define RECT_HEIGHT(r) (r.bottom - r.top) + +#define HR_FAILED_AND(hr,func,and) \ + G_STMT_START { \ + if (FAILED (hr)) { \ + gchar *msg = get_hresult_to_string (hr); \ + GST_ERROR_OBJECT (src, #func " failed (%x): %s", (guint) hr, msg); \ + g_free (msg); \ + and; \ + } \ + } G_STMT_END + +#define HR_FAILED_RET(hr,func,ret) HR_FAILED_AND(hr,func,return ret) + +#define HR_FAILED_GOTO(hr,func,where) HR_FAILED_AND(hr,func,goto where) + +#define HR_FAILED_INFO(hr, func) \ + G_STMT_START { \ + if (FAILED (hr)) { \ + gchar *msg = get_hresult_to_string (hr); \ + GST_INFO_OBJECT (src, #func " failed (%x): %s", (guint) hr, msg); \ + g_free (msg); \ + } \ + } G_STMT_END + + +typedef struct _GstDXGIScreenCapSrc GstDXGIScreenCapSrc; + +typedef struct _DxgiCapture DxgiCapture; + +DxgiCapture *dxgicap_new (HMONITOR monitor, GstDXGIScreenCapSrc * src); +void dxgicap_destory (DxgiCapture * _this); + +gboolean dxgicap_start (DxgiCapture * _this); +void dxgicap_stop (DxgiCapture * _this); + +gint dxgicap_acquire_next_frame (DxgiCapture * _this, gboolean show_cursor, + guint timeout); +gboolean dxgicap_copy_buffer (DxgiCapture * _this, gboolean show_cursor, + LPRECT src_rect, GstVideoInfo * video_info, GstBuffer * buf); + +HMONITOR get_hmonitor_by_device_name (const gchar * device_name); +HMONITOR get_hmonitor_primary (void); +HMONITOR get_hmonitor_by_index (int index); + +gboolean get_monitor_physical_size (HMONITOR hmonitor, LPRECT rect); + +gchar *get_hresult_to_string (HRESULT hr); + +#endif /* __DXGICAP_H__ */ diff --git a/sys/winscreencap/gstdxgiscreencapsrc.c b/sys/winscreencap/gstdxgiscreencapsrc.c new file mode 100644 index 0000000..e6e4870 --- /dev/null +++ b/sys/winscreencap/gstdxgiscreencapsrc.c @@ -0,0 +1,564 @@ +/* GStreamer + * Copyright (C) 2019 OKADA Jun-ichi + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-dxgiscreencapsrc + * @title: dxgiscreencapsrc + * + * This element uses DXGI Desktop Duplication API. + * The default is capturing the whole desktop, but #GstDXGIScreenCapSrc:x, + * #GstDXGIScreenCapSrc:y, #GstDXGIScreenCapSrc:width and + * #GstDXGIScreenCapSrc:height can be used to select a particular region. + * Use #GstDXGIScreenCapSrc:monitor for changing which monitor to capture + * from. + * + * ## Example pipelines + * |[ + * gst-launch-1.0 dxgiscreencapsrc ! videoconvert ! dshowvideosink + * ]| Capture the desktop and display it. + * |[ + * gst-launch-1.0 dxgiscreencapsrc x=100 y=100 width=320 height=240 ! + * videoconvert ! dshowvideosink + * ]| Capture a portion of the desktop and display it. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstdxgiscreencapsrc.h" +#include "dxgicapture.h" + +GST_DEBUG_CATEGORY_STATIC (dxgiscreencapsrc_debug); + +struct _GstDXGIScreenCapSrc +{ + /* Parent */ + GstPushSrc src; + + /* Properties */ + gint capture_x; + gint capture_y; + gint capture_w; + gint capture_h; + guint monitor; + gchar *device_name; + gboolean show_cursor; + + /* Source pad frame rate */ + gint rate_numerator; + gint rate_denominator; + + /* Runtime variables */ + RECT screen_rect; + RECT src_rect; + guint64 frame_number; + GstClockID clock_id; + GstVideoInfo video_info; + + /*DXGI capture */ + DxgiCapture *dxgi_capture; +}; + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("BGRA"))); + +#define gst_dxgi_screen_cap_src_parent_class parent_class +G_DEFINE_TYPE (GstDXGIScreenCapSrc, gst_dxgi_screen_cap_src, GST_TYPE_PUSH_SRC); + +#define DEFAULT_MONITOR (-1) +#define DEFAULT_DEVICE_NAME (NULL) +#define DEFAULT_SHOW_CURSOR (FALSE) +#define DEFAULT_X_POS (0) +#define DEFAULT_Y_POS (0) +#define DEFAULT_WIDTH (0) +#define DEFAULT_HEIGHT (0) + +enum +{ + PROP_0, + PROP_MONITOR, + PROP_DEVICE_NAME, + PROP_SHOW_CURSOR, + PROP_X_POS, + PROP_Y_POS, + PROP_WIDTH, + PROP_HEIGHT +}; + +static void gst_dxgi_screen_cap_src_dispose (GObject * object); +static void gst_dxgi_screen_cap_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_dxgi_screen_cap_src_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstCaps *gst_dxgi_screen_cap_src_fixate (GstBaseSrc * bsrc, + GstCaps * caps); +static gboolean gst_dxgi_screen_cap_src_set_caps (GstBaseSrc * bsrc, + GstCaps * caps); +static GstCaps *gst_dxgi_screen_cap_src_get_caps (GstBaseSrc * bsrc, + GstCaps * filter); +static gboolean gst_dxgi_screen_cap_src_start (GstBaseSrc * bsrc); +static gboolean gst_dxgi_screen_cap_src_stop (GstBaseSrc * bsrc); + +static gboolean gst_dxgi_screen_cap_src_unlock (GstBaseSrc * bsrc); + +static GstFlowReturn gst_dxgi_screen_cap_src_fill (GstPushSrc * pushsrc, + GstBuffer * buffer); + + +static HMONITOR _get_hmonitor (GstDXGIScreenCapSrc * src); + +/* Implementation. */ +static void +gst_dxgi_screen_cap_src_class_init (GstDXGIScreenCapSrcClass * klass) +{ + GObjectClass *go_class; + GstElementClass *e_class; + GstBaseSrcClass *bs_class; + GstPushSrcClass *ps_class; + + go_class = G_OBJECT_CLASS (klass); + e_class = GST_ELEMENT_CLASS (klass); + bs_class = GST_BASE_SRC_CLASS (klass); + ps_class = GST_PUSH_SRC_CLASS (klass); + + go_class->set_property = gst_dxgi_screen_cap_src_set_property; + go_class->get_property = gst_dxgi_screen_cap_src_get_property; + + go_class->dispose = GST_DEBUG_FUNCPTR (gst_dxgi_screen_cap_src_dispose); + bs_class->get_caps = GST_DEBUG_FUNCPTR (gst_dxgi_screen_cap_src_get_caps); + bs_class->set_caps = GST_DEBUG_FUNCPTR (gst_dxgi_screen_cap_src_set_caps); + bs_class->start = GST_DEBUG_FUNCPTR (gst_dxgi_screen_cap_src_start); + bs_class->stop = GST_DEBUG_FUNCPTR (gst_dxgi_screen_cap_src_stop); + bs_class->unlock = GST_DEBUG_FUNCPTR (gst_dxgi_screen_cap_src_unlock); + bs_class->fixate = GST_DEBUG_FUNCPTR (gst_dxgi_screen_cap_src_fixate); + ps_class->fill = GST_DEBUG_FUNCPTR (gst_dxgi_screen_cap_src_fill); + + g_object_class_install_property (go_class, PROP_MONITOR, + g_param_spec_int ("monitor", "Monitor", + "Which monitor to use (-1 = primary monitor and default)", + DEFAULT_MONITOR, G_MAXINT, DEFAULT_MONITOR, G_PARAM_READWRITE)); + g_object_class_install_property (go_class, PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Monitor device name", + "Which monitor to use by device name (e.g. \"\\\\\\\\.\\\\DISPLAY1\")", + DEFAULT_DEVICE_NAME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (go_class, PROP_SHOW_CURSOR, + g_param_spec_boolean ("cursor", + "Show mouse cursor", + "Whether to show mouse cursor (default off)", + DEFAULT_SHOW_CURSOR, G_PARAM_READWRITE)); + g_object_class_install_property (go_class, PROP_X_POS, + g_param_spec_int ("x", "X", + "Horizontal coordinate of top left corner for the screen capture " + "area", 0, G_MAXINT, DEFAULT_X_POS, G_PARAM_READWRITE)); + g_object_class_install_property (go_class, PROP_Y_POS, + g_param_spec_int ("y", "Y", + "Vertical coordinate of top left corner for the screen capture " + "area", 0, G_MAXINT, DEFAULT_Y_POS, G_PARAM_READWRITE)); + g_object_class_install_property (go_class, PROP_WIDTH, + g_param_spec_int ("width", "Width", + "Width of screen capture area (0 = maximum)", + 0, G_MAXINT, DEFAULT_WIDTH, G_PARAM_READWRITE)); + g_object_class_install_property (go_class, PROP_HEIGHT, + g_param_spec_int ("height", "Height", + "Height of screen capture area (0 = maximum)", + 0, G_MAXINT, DEFAULT_HEIGHT, G_PARAM_READWRITE)); + + gst_element_class_add_static_pad_template (e_class, &src_template); + + gst_element_class_set_static_metadata (e_class, + "DirectX DXGI screen capture source", + "Source/Video", "Captures screen", "OKADA Jun-ichi "); + + GST_DEBUG_CATEGORY_INIT (dxgiscreencapsrc_debug, "dxgiscreencapsrc", 0, + "DirectX DXGI screen capture source"); +} + +static void +gst_dxgi_screen_cap_src_init (GstDXGIScreenCapSrc * src) +{ + /* Set src element inital values... */ + src->capture_x = DEFAULT_X_POS; + src->capture_y = DEFAULT_Y_POS; + src->capture_w = DEFAULT_WIDTH; + src->capture_h = DEFAULT_HEIGHT; + + src->monitor = DEFAULT_MONITOR; + src->device_name = DEFAULT_DEVICE_NAME; + src->show_cursor = DEFAULT_SHOW_CURSOR; + + src->dxgi_capture = NULL; + + gst_base_src_set_live (GST_BASE_SRC (src), TRUE); + gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); +} + +static void +gst_dxgi_screen_cap_src_dispose (GObject * object) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (object); + + g_free (src->device_name); + src->device_name = NULL; + + dxgicap_destory (src->dxgi_capture); + src->dxgi_capture = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_dxgi_screen_cap_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (object); + + switch (prop_id) { + case PROP_MONITOR: + src->monitor = g_value_get_int (value); + break; + case PROP_DEVICE_NAME: + g_free (src->device_name); + src->device_name = g_value_dup_string (value); + break; + case PROP_SHOW_CURSOR: + src->show_cursor = g_value_get_boolean (value); + break; + case PROP_X_POS: + src->capture_x = g_value_get_int (value); + break; + case PROP_Y_POS: + src->capture_y = g_value_get_int (value); + break; + case PROP_WIDTH: + src->capture_w = g_value_get_int (value); + break; + case PROP_HEIGHT: + src->capture_h = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + +static void +gst_dxgi_screen_cap_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (object); + + switch (prop_id) { + case PROP_MONITOR: + g_value_set_int (value, src->monitor); + break; + case PROP_DEVICE_NAME: + g_value_set_string (value, src->device_name); + break; + case PROP_SHOW_CURSOR: + g_value_set_boolean (value, src->show_cursor); + break; + case PROP_X_POS: + g_value_set_int (value, src->capture_x); + break; + case PROP_Y_POS: + g_value_set_int (value, src->capture_y); + break; + case PROP_WIDTH: + g_value_set_int (value, src->capture_w); + break; + case PROP_HEIGHT: + g_value_set_int (value, src->capture_h); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + +static GstCaps * +gst_dxgi_screen_cap_src_fixate (GstBaseSrc * bsrc, GstCaps * caps) +{ + GstStructure *structure; + + caps = gst_caps_make_writable (caps); + + structure = gst_caps_get_structure (caps, 0); + + gst_structure_fixate_field_nearest_int (structure, "width", 640); + gst_structure_fixate_field_nearest_int (structure, "height", 480); + gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1); + + caps = GST_BASE_SRC_CLASS (parent_class)->fixate (bsrc, caps); + + return caps; +} + +static gboolean +gst_dxgi_screen_cap_src_set_caps (GstBaseSrc * bsrc, GstCaps * caps) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (bsrc); + GstStructure *structure; + + structure = gst_caps_get_structure (caps, 0); + + src->src_rect = src->screen_rect; + if (src->capture_w && src->capture_h) { + src->src_rect.left += src->capture_x; + src->src_rect.top += src->capture_y; + src->src_rect.right = src->src_rect.left + src->capture_w; + src->src_rect.bottom = src->src_rect.top + src->capture_h; + } + + gst_structure_get_fraction (structure, "framerate", + &src->rate_numerator, &src->rate_denominator); + + GST_DEBUG_OBJECT (src, "set_caps size %dx%d, %d/%d fps", + (gint) RECT_WIDTH (src->src_rect), + (gint) RECT_HEIGHT (src->src_rect), + src->rate_numerator, src->rate_denominator); + + gst_video_info_from_caps (&src->video_info, caps); + gst_base_src_set_blocksize (bsrc, GST_VIDEO_INFO_SIZE (&src->video_info)); + return TRUE; +} + +static GstCaps * +gst_dxgi_screen_cap_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (bsrc); + RECT rect_dst; + GstCaps *caps = NULL; + + HMONITOR hmonitor = _get_hmonitor (src); + if (!get_monitor_physical_size (hmonitor, &rect_dst)) { + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, + ("Specified monitor with index %d not found", src->monitor), (NULL)); + return NULL; + } + + src->screen_rect = rect_dst; + if (src->capture_w && src->capture_h && + src->capture_x + src->capture_w <= RECT_WIDTH (rect_dst) && + src->capture_y + src->capture_h <= RECT_HEIGHT (rect_dst)) { + rect_dst.left = src->capture_x; + rect_dst.top = src->capture_y; + rect_dst.right = src->capture_x + src->capture_w; + rect_dst.bottom = src->capture_y + src->capture_h; + } else { + /* Default values */ + src->capture_x = src->capture_y = 0; + src->capture_w = src->capture_h = 0; + } + + /* The desktop image is always in the DXGI_FORMAT_B8G8R8A8_UNORM format. */ + GST_DEBUG_OBJECT (src, "get_cap rect: %ld, %ld, %ld, %ld", rect_dst.left, + rect_dst.top, rect_dst.right, rect_dst.bottom); + + caps = + gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, "BGRA", + "width", G_TYPE_INT, RECT_WIDTH (rect_dst), + "height", G_TYPE_INT, RECT_HEIGHT (rect_dst), + "framerate", GST_TYPE_FRACTION_RANGE, 1, 1, G_MAXINT, + 1, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL); + + if (filter) { + GstCaps *tmp = + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + caps = tmp; + } + return caps; +} + +static gboolean +gst_dxgi_screen_cap_src_start (GstBaseSrc * bsrc) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (bsrc); + HMONITOR hmonitor = _get_hmonitor (src); + if (NULL == hmonitor) { + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, + ("Specified monitor with index %d not found", src->monitor), (NULL)); + return FALSE; + } + src->dxgi_capture = dxgicap_new (hmonitor, src); + + if (NULL == src->dxgi_capture) { + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, + ("Specified monitor with index %d not found", src->monitor), (NULL)); + return FALSE; + } + dxgicap_start (src->dxgi_capture); + + src->frame_number = -1; + return TRUE; +} + +static gboolean +gst_dxgi_screen_cap_src_stop (GstBaseSrc * bsrc) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (bsrc); + dxgicap_stop (src->dxgi_capture); + dxgicap_destory (src->dxgi_capture); + src->dxgi_capture = NULL; + + return TRUE; +} + +static gboolean +gst_dxgi_screen_cap_src_unlock (GstBaseSrc * bsrc) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (bsrc); + + GST_OBJECT_LOCK (src); + if (src->clock_id) { + GST_DEBUG_OBJECT (src, "Waking up waiting clock"); + gst_clock_id_unschedule (src->clock_id); + } + GST_OBJECT_UNLOCK (src); + + return TRUE; +} + +static GstFlowReturn +gst_dxgi_screen_cap_src_fill (GstPushSrc * push_src, GstBuffer * buf) +{ + GstDXGIScreenCapSrc *src = GST_DXGI_SCREEN_CAP_SRC (push_src); + GstClock *clock; + GstClockTime buf_time, buf_dur; + guint64 frame_number; + + if (G_UNLIKELY (!src->dxgi_capture)) { + GST_DEBUG_OBJECT (src, "format wasn't negotiated before create function"); + return GST_FLOW_NOT_NEGOTIATED; + } + + clock = gst_element_get_clock (GST_ELEMENT (src)); + if (clock != NULL) { + GstClockTime time, base_time; + + /* Calculate sync time. */ + + time = gst_clock_get_time (clock); + base_time = gst_element_get_base_time (GST_ELEMENT (src)); + buf_time = time - base_time; + + if (src->rate_numerator) { + frame_number = gst_util_uint64_scale (buf_time, + src->rate_numerator, GST_SECOND * src->rate_denominator); + } else { + frame_number = -1; + } + } else { + buf_time = GST_CLOCK_TIME_NONE; + frame_number = -1; + } + + if (frame_number != -1 && frame_number == src->frame_number) { + GstClockID id; + GstClockReturn ret; + + /* Need to wait for the next frame */ + frame_number += 1; + + /* Figure out what the next frame time is */ + buf_time = gst_util_uint64_scale (frame_number, + src->rate_denominator * GST_SECOND, src->rate_numerator); + + id = gst_clock_new_single_shot_id (clock, + buf_time + gst_element_get_base_time (GST_ELEMENT (src))); + GST_OBJECT_LOCK (src); + src->clock_id = id; + GST_OBJECT_UNLOCK (src); + + GST_DEBUG_OBJECT (src, "Waiting for next frame time %" G_GUINT64_FORMAT, + buf_time); + ret = gst_clock_id_wait (id, NULL); + + GST_OBJECT_LOCK (src); + gst_clock_id_unref (id); + src->clock_id = NULL; + GST_OBJECT_UNLOCK (src); + + if (ret == GST_CLOCK_UNSCHEDULED) { + /* Got woken up by the unlock function */ + if (clock) { + gst_object_unref (clock); + } + return GST_FLOW_FLUSHING; + } + + /* Duration is a complete 1/fps frame duration */ + buf_dur = + gst_util_uint64_scale_int (GST_SECOND, src->rate_denominator, + src->rate_numerator); + } else if (frame_number != -1) { + GstClockTime next_buf_time; + + GST_DEBUG_OBJECT (src, "No need to wait for next frame time %" + G_GUINT64_FORMAT " next frame = %" G_GINT64_FORMAT + " prev = %" G_GINT64_FORMAT, buf_time, frame_number, src->frame_number); + next_buf_time = + gst_util_uint64_scale (frame_number + 1, + src->rate_denominator * GST_SECOND, src->rate_numerator); + /* Frame duration is from now until the next expected capture time */ + buf_dur = next_buf_time - buf_time; + } else { + buf_dur = GST_CLOCK_TIME_NONE; + } + src->frame_number = frame_number; + + if (clock) { + gst_object_unref (clock); + } + + /* Get the latest desktop frame. */ + if (dxgicap_acquire_next_frame (src->dxgi_capture, src->show_cursor, 0)) { + /* Copy the latest desktop frame to the video frame. */ + if (dxgicap_copy_buffer (src->dxgi_capture, src->show_cursor, + &src->src_rect, &src->video_info, buf)) { + GST_BUFFER_TIMESTAMP (buf) = buf_time; + GST_BUFFER_DURATION (buf) = buf_dur; + return GST_FLOW_OK; + } + } + return GST_FLOW_ERROR; +} + +static HMONITOR +_get_hmonitor (GstDXGIScreenCapSrc * src) +{ + HMONITOR hmonitor = NULL; + GST_DEBUG_OBJECT (src, "device_name:%s", GST_STR_NULL (src->device_name)); + if (NULL != src->device_name) { + hmonitor = get_hmonitor_by_device_name (src->device_name); + } + if (NULL == hmonitor && DEFAULT_MONITOR != src->monitor) { + hmonitor = get_hmonitor_by_index (src->monitor); + } + if (NULL == hmonitor) { + hmonitor = get_hmonitor_primary (); + } + return hmonitor; +} diff --git a/sys/winscreencap/gstdxgiscreencapsrc.h b/sys/winscreencap/gstdxgiscreencapsrc.h new file mode 100644 index 0000000..4e5fa22 --- /dev/null +++ b/sys/winscreencap/gstdxgiscreencapsrc.h @@ -0,0 +1,32 @@ +/* GStreamer + * Copyright (C) 2019 OKADA Jun-ichi + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef __GST_DXGI_SCREEN_CAP_SRC_H__ +#define __GST_DXGI_SCREEN_CAP_SRC_H__ + +#include +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_DXGI_SCREEN_CAP_SRC (gst_dxgi_screen_cap_src_get_type()) +G_DECLARE_FINAL_TYPE (GstDXGIScreenCapSrc, gst_dxgi_screen_cap_src, GST, + DXGI_SCREEN_CAP_SRC, GstPushSrc); + +G_END_DECLS +#endif /* __GST_DXGI_SCREEN_CAP_SRC_H__ */ diff --git a/sys/winscreencap/gstwinscreencap.c b/sys/winscreencap/gstwinscreencap.c index fb6b332..5d49b6d 100644 --- a/sys/winscreencap/gstwinscreencap.c +++ b/sys/winscreencap/gstwinscreencap.c @@ -20,11 +20,15 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif - #include "gstwinscreencap.h" #include "gstgdiscreencapsrc.h" #include "gstdx9screencapsrc.h" +#ifdef HAVE_DXGI_CAP +#include +#include "gstdxgiscreencapsrc.h" +#endif + static BOOL CALLBACK _diplay_monitor_enum (HMONITOR hMon, HDC hdc, LPRECT rect, LPARAM param) { @@ -66,6 +70,15 @@ plugin_init (GstPlugin * plugin) GST_RANK_NONE, GST_TYPE_DX9SCREENCAPSRC)) { return FALSE; } +#ifdef HAVE_DXGI_CAP + if (IsWindows8OrGreater ()) { + /* dxgiscreencapsrc is needs Windows8 or later. */ + if (!gst_element_register (plugin, "dxgiscreencapsrc", + GST_RANK_NONE, GST_TYPE_DXGI_SCREEN_CAP_SRC)) { + return FALSE; + } + } +#endif return TRUE; } diff --git a/sys/winscreencap/meson.build b/sys/winscreencap/meson.build index 4e3ef15..11bf5da 100644 --- a/sys/winscreencap/meson.build +++ b/sys/winscreencap/meson.build @@ -4,23 +4,47 @@ winscreencap_sources = [ 'gstwinscreencap.c', ] +have_dxgi = false +dxgi_c_args = [] +dxgiscreencap_sources = [] +dxgi_dep = [] + if host_system != 'windows' or get_option('winscreencap').disabled() subdir_done() endif d3d_dep = cc.find_library('d3d9', required : get_option('winscreencap')) gdi_dep = cc.find_library('gdi32', required : get_option('winscreencap')) +d3d11_dep = cc.find_library('d3d11', required : false) +dxgi_lib_dep = cc.find_library('dxgi', required : false) +d3d_compile_dep = cc.find_library('d3dcompiler', required : false) +windowscodecs_dep = cc.find_library('windowscodecs', required : false) +dxguid_dep = cc.find_library('dxguid', required : false) + +have_d3d11_h = cc.has_header('d3d11.h') +have_dxgi_h = cc.has_header('dxgi1_2.h') +have_d3dcompiler_h = cc.has_header('d3dcompiler.h') +have_versionhelpers_h = cc.has_header('versionhelpers.h') + have_d3d9_h = cc.has_header('d3d9.h') if not have_d3d9_h and get_option('winscreencap').enabled() error('winscreencap plugin enabled but d3d9.h not found') endif +have_dxgi = d3d11_dep.found() and dxgi_lib_dep.found() and d3d_compile_dep.found() and windowscodecs_dep.found() and dxguid_dep.found() and have_d3d11_h and have_dxgi_h and have_d3dcompiler_h and have_versionhelpers_h + +if have_dxgi + dxgi_c_args += ['-DHAVE_DXGI_CAP'] + dxgiscreencap_sources += ['dxgicapture.c', 'gstdxgiscreencapsrc.c'] + dxgi_dep += [d3d11_dep, dxgi_lib_dep, d3d_compile_dep, windowscodecs_dep, dxguid_dep] +endif + if d3d_dep.found() and gdi_dep.found() and have_d3d9_h gstwinscreencap = library('gstwinscreencap', - winscreencap_sources, - c_args : gst_plugins_bad_args, + winscreencap_sources + dxgiscreencap_sources, + c_args : gst_plugins_bad_args + dxgi_c_args, include_directories : [configinc], - dependencies : [gstbase_dep, gstvideo_dep, d3d_dep, gdi_dep], + dependencies : [gstbase_dep, gstvideo_dep, d3d_dep, gdi_dep] + dxgi_dep, install : true, install_dir : plugins_install_dir, ) -- 2.7.4