summaryrefslogtreecommitdiff
path: root/libavfilter/vsrc_ddagrab.c
diff options
context:
space:
mode:
authorTimo Rothenpieler <timo@rothenpieler.org>2022-07-08 18:02:40 +0200
committerTimo Rothenpieler <timo@rothenpieler.org>2022-07-18 02:08:27 +0200
commitf611255480b7cb61af745131251a1fd72f4dd086 (patch)
tree78663267da664062a95f95490af4965cac4aa908 /libavfilter/vsrc_ddagrab.c
parent2f0d45571b5290f2cc7269cc74bfb5af139eff3e (diff)
downloadffmpeg-f611255480b7cb61af745131251a1fd72f4dd086.tar.gz
avfilter: add vsrc_ddagrab
Diffstat (limited to 'libavfilter/vsrc_ddagrab.c')
-rw-r--r--libavfilter/vsrc_ddagrab.c982
1 files changed, 982 insertions, 0 deletions
diff --git a/libavfilter/vsrc_ddagrab.c b/libavfilter/vsrc_ddagrab.c
new file mode 100644
index 0000000000..4a90ad7a02
--- /dev/null
+++ b/libavfilter/vsrc_ddagrab.c
@@ -0,0 +1,982 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0A00
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0A00
+#endif
+#define WIN32_LEAN_AND_MEAN
+
+#include <windows.h>
+
+#define COBJMACROS
+
+#include <initguid.h>
+#include <d3d11.h>
+#include <dxgi1_2.h>
+#if HAVE_IDXGIOUTPUT5
+#include <dxgi1_5.h>
+#endif
+
+#include "libavutil/opt.h"
+#include "libavutil/time.h"
+#include "libavutil/avstring.h"
+#include "libavutil/avassert.h"
+#include "libavutil/hwcontext.h"
+#include "libavutil/hwcontext_d3d11va.h"
+#include "avfilter.h"
+#include "internal.h"
+#include "formats.h"
+#include "video.h"
+
+#include "vsrc_ddagrab_shaders.h"
+
+// avutil/time.h takes and returns time in microseconds
+#define TIMER_RES 1000000
+#define TIMER_RES64 INT64_C(1000000)
+
+typedef struct DdagrabContext {
+ const AVClass *class;
+
+ AVBufferRef *device_ref;
+ AVHWDeviceContext *device_ctx;
+ AVD3D11VADeviceContext *device_hwctx;
+
+ AVBufferRef *frames_ref;
+ AVHWFramesContext *frames_ctx;
+ AVD3D11VAFramesContext *frames_hwctx;
+
+ DXGI_OUTPUT_DESC output_desc;
+ IDXGIOutputDuplication *dxgi_outdupl;
+ AVFrame *last_frame;
+
+ int mouse_x, mouse_y;
+ ID3D11Texture2D *mouse_texture;
+ ID3D11ShaderResourceView* mouse_resource_view ;
+
+ AVRational time_base;
+ int64_t time_frame;
+ int64_t time_timeout;
+ int64_t first_pts;
+
+ DXGI_FORMAT raw_format;
+ int raw_width;
+ int raw_height;
+
+ ID3D11Texture2D *probed_texture;
+
+ ID3D11VertexShader *vertex_shader;
+ ID3D11InputLayout *input_layout;
+ ID3D11PixelShader *pixel_shader;
+ ID3D11Buffer *const_buffer;
+ ID3D11SamplerState *sampler_state;
+ ID3D11BlendState *blend_state;
+
+ int output_idx;
+ int draw_mouse;
+ AVRational framerate;
+ int width;
+ int height;
+ int offset_x;
+ int offset_y;
+} DdagrabContext;
+
+#define OFFSET(x) offsetof(DdagrabContext, x)
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+static const AVOption ddagrab_options[] = {
+ { "output_idx", "dda output index to capture", OFFSET(output_idx), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
+ { "draw_mouse", "draw the mouse pointer", OFFSET(draw_mouse), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS },
+ { "framerate", "set video frame rate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, { .str = "30" }, 0, INT_MAX, FLAGS },
+ { "video_size", "set video frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, 0, FLAGS },
+ { "offset_x", "capture area x offset", OFFSET(offset_x), AV_OPT_TYPE_INT, { .i64 = 0 }, INT_MIN, INT_MAX, FLAGS },
+ { "offset_y", "capture area y offset", OFFSET(offset_y), AV_OPT_TYPE_INT, { .i64 = 0 }, INT_MIN, INT_MAX, FLAGS },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(ddagrab);
+
+static inline void release_resource(void *resource)
+{
+ IUnknown **resp = (IUnknown**)resource;
+ if (*resp) {
+ IUnknown_Release(*resp);
+ *resp = NULL;
+ }
+}
+
+static av_cold void ddagrab_uninit(AVFilterContext *avctx)
+{
+ DdagrabContext *dda = avctx->priv;
+
+ release_resource(&dda->blend_state);
+ release_resource(&dda->sampler_state);
+ release_resource(&dda->pixel_shader);
+ release_resource(&dda->input_layout);
+ release_resource(&dda->vertex_shader);
+ release_resource(&dda->const_buffer);
+
+ release_resource(&dda->probed_texture);
+
+ release_resource(&dda->dxgi_outdupl);
+ release_resource(&dda->mouse_resource_view);
+ release_resource(&dda->mouse_texture);
+
+ av_frame_free(&dda->last_frame);
+ av_buffer_unref(&dda->frames_ref);
+ av_buffer_unref(&dda->device_ref);
+}
+
+static av_cold int init_dxgi_dda(AVFilterContext *avctx)
+{
+ DdagrabContext *dda = avctx->priv;
+ IDXGIDevice *dxgi_device = NULL;
+ IDXGIAdapter *dxgi_adapter = NULL;
+ IDXGIOutput *dxgi_output = NULL;
+ IDXGIOutput1 *dxgi_output1 = NULL;
+#if HAVE_IDXGIOUTPUT5 && HAVE_SETTHREADDPIAWARENESSCONTEXT
+ IDXGIOutput5 *dxgi_output5 = NULL;
+#endif
+ int w, h;
+ HRESULT hr;
+
+ hr = ID3D11Device_QueryInterface(dda->device_hwctx->device, &IID_IDXGIDevice, (void**)&dxgi_device);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "Failed querying IDXGIDevice\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ hr = IDXGIDevice_GetParent(dxgi_device, &IID_IDXGIAdapter, (void**)&dxgi_adapter);
+ IDXGIDevice_Release(dxgi_device);
+ dxgi_device = NULL;
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "Failed getting parent IDXGIAdapter\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ hr = IDXGIAdapter_EnumOutputs(dxgi_adapter, dda->output_idx, &dxgi_output);
+ IDXGIAdapter_Release(dxgi_adapter);
+ dxgi_adapter = NULL;
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to enumerate DXGI output %d\n", dda->output_idx);
+ return AVERROR_EXTERNAL;
+ }
+
+ hr = IDXGIOutput_GetDesc(dxgi_output, &dda->output_desc);
+ if (FAILED(hr)) {
+ IDXGIOutput_Release(dxgi_output);
+ av_log(avctx, AV_LOG_ERROR, "Failed getting output description\n");
+ return AVERROR_EXTERNAL;
+ }
+
+#if HAVE_IDXGIOUTPUT5 && HAVE_SETTHREADDPIAWARENESSCONTEXT
+ hr = IDXGIOutput_QueryInterface(dxgi_output, &IID_IDXGIOutput5, (void**)&dxgi_output5);
+ if (SUCCEEDED(hr)) {
+ DPI_AWARENESS_CONTEXT prev_dpi_ctx;
+ DXGI_FORMAT formats[] = {
+ DXGI_FORMAT_R10G10B10A2_UNORM,
+ DXGI_FORMAT_B8G8R8A8_UNORM
+ };
+
+ IDXGIOutput_Release(dxgi_output);
+ dxgi_output = NULL;
+
+ prev_dpi_ctx = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+ if (!prev_dpi_ctx)
+ av_log(avctx, AV_LOG_WARNING, "Failed enabling DPI awareness for DDA\n");
+
+ hr = IDXGIOutput5_DuplicateOutput1(dxgi_output5,
+ (IUnknown*)dda->device_hwctx->device,
+ 0,
+ FF_ARRAY_ELEMS(formats),
+ formats,
+ &dda->dxgi_outdupl);
+ IDXGIOutput5_Release(dxgi_output5);
+ dxgi_output5 = NULL;
+
+ if (prev_dpi_ctx)
+ SetThreadDpiAwarenessContext(prev_dpi_ctx);
+ } else {
+ av_log(avctx, AV_LOG_DEBUG, "Falling back to IDXGIOutput1\n");
+#else
+ {
+#endif
+ hr = IDXGIOutput_QueryInterface(dxgi_output, &IID_IDXGIOutput1, (void**)&dxgi_output1);
+ IDXGIOutput_Release(dxgi_output);
+ dxgi_output = NULL;
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "Failed querying IDXGIOutput1\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ hr = IDXGIOutput1_DuplicateOutput(dxgi_output1,
+ (IUnknown*)dda->device_hwctx->device,
+ &dda->dxgi_outdupl);
+ IDXGIOutput1_Release(dxgi_output1);
+ dxgi_output1 = NULL;
+ }
+
+ if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) {
+ av_log(avctx, AV_LOG_ERROR, "Too many open duplication sessions\n");
+ return AVERROR(EBUSY);
+ } else if (hr == DXGI_ERROR_UNSUPPORTED) {
+ av_log(avctx, AV_LOG_ERROR, "Selected output not supported\n");
+ return AVERROR_EXTERNAL;
+ } else if (hr == E_INVALIDARG) {
+ av_log(avctx, AV_LOG_ERROR, "Invalid output duplication argument\n");
+ return AVERROR(EINVAL);
+ } else if (hr == E_ACCESSDENIED) {
+ av_log(avctx, AV_LOG_ERROR, "Desktop duplication access denied\n");
+ return AVERROR(EPERM);
+ } else if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "Failed duplicating output\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ w = dda->output_desc.DesktopCoordinates.right - dda->output_desc.DesktopCoordinates.left;
+ h = dda->output_desc.DesktopCoordinates.bottom - dda->output_desc.DesktopCoordinates.top;
+ av_log(avctx, AV_LOG_VERBOSE, "Opened dxgi output %d with dimensions %dx%d\n", dda->output_idx, w, h);
+
+ return 0;
+}
+
+typedef struct ConstBufferData
+{
+ float width;
+ float height;
+
+ uint64_t padding;
+} ConstBufferData;
+
+static const D3D11_INPUT_ELEMENT_DESC vertex_shader_input_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 av_cold int init_render_resources(AVFilterContext *avctx)
+{
+ DdagrabContext *dda = avctx->priv;
+ ID3D11Device *dev = dda->device_hwctx->device;
+ D3D11_SAMPLER_DESC sampler_desc = { 0 };
+ D3D11_BLEND_DESC blend_desc = { 0 };
+ D3D11_BUFFER_DESC buffer_desc = { 0 };
+ D3D11_SUBRESOURCE_DATA buffer_data = { 0 };
+ ConstBufferData const_data = { 0 };
+ HRESULT hr;
+
+ hr = ID3D11Device_CreateVertexShader(dev,
+ vertex_shader_bytes,
+ FF_ARRAY_ELEMS(vertex_shader_bytes),
+ NULL,
+ &dda->vertex_shader);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "CreateVertexShader failed: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ hr = ID3D11Device_CreateInputLayout(dev,
+ vertex_shader_input_layout,
+ FF_ARRAY_ELEMS(vertex_shader_input_layout),
+ vertex_shader_bytes,
+ FF_ARRAY_ELEMS(vertex_shader_bytes),
+ &dda->input_layout);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "CreateInputLayout failed: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ hr = ID3D11Device_CreatePixelShader(dev,
+ pixel_shader_bytes,
+ FF_ARRAY_ELEMS(pixel_shader_bytes),
+ NULL,
+ &dda->pixel_shader);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "CreatePixelShader failed: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ const_data = (ConstBufferData){ dda->width, dda->height };
+
+ buffer_data.pSysMem = &const_data;
+ buffer_desc.ByteWidth = sizeof(const_data);
+ buffer_desc.Usage = D3D11_USAGE_IMMUTABLE;
+ buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
+ hr = ID3D11Device_CreateBuffer(dev,
+ &buffer_desc,
+ &buffer_data,
+ &dda->const_buffer);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "CreateBuffer const buffer failed: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ 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;
+ hr = ID3D11Device_CreateSamplerState(dev,
+ &sampler_desc,
+ &dda->sampler_state);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "CreateSamplerState failed: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ blend_desc.AlphaToCoverageEnable = FALSE;
+ blend_desc.IndependentBlendEnable = FALSE;
+ blend_desc.RenderTarget[0].BlendEnable = TRUE;
+ blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
+ blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
+ blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
+ blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
+ blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
+ blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
+ blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
+ hr = ID3D11Device_CreateBlendState(dev,
+ &blend_desc,
+ &dda->blend_state);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "CreateBlendState failed: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ return 0;
+}
+
+static av_cold int ddagrab_init(AVFilterContext *avctx)
+{
+ DdagrabContext *dda = avctx->priv;
+ int ret = 0;
+
+ if (avctx->hw_device_ctx) {
+ dda->device_ctx = (AVHWDeviceContext*)avctx->hw_device_ctx->data;
+
+ if (dda->device_ctx->type != AV_HWDEVICE_TYPE_D3D11VA) {
+ av_log(avctx, AV_LOG_ERROR, "Non-D3D11VA input hw_device_ctx\n");
+ return AVERROR(EINVAL);
+ }
+
+ dda->device_ref = av_buffer_ref(avctx->hw_device_ctx);
+ if (!dda->device_ref)
+ return AVERROR(ENOMEM);
+
+ av_log(avctx, AV_LOG_VERBOSE, "Using provided hw_device_ctx\n");
+ } else {
+ ret = av_hwdevice_ctx_create(&dda->device_ref, AV_HWDEVICE_TYPE_D3D11VA, NULL, NULL, 0);
+ if (ret < 0) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to create D3D11VA device.\n");
+ return ret;
+ }
+
+ dda->device_ctx = (AVHWDeviceContext*)dda->device_ref->data;
+
+ av_log(avctx, AV_LOG_VERBOSE, "Created internal hw_device_ctx\n");
+ }
+
+ dda->device_hwctx = (AVD3D11VADeviceContext*)dda->device_ctx->hwctx;
+
+ ret = init_dxgi_dda(avctx);
+ if (ret < 0)
+ goto fail;
+
+ dda->time_base = av_inv_q(dda->framerate);
+ dda->time_frame = av_gettime_relative() / av_q2d(dda->time_base);
+ dda->time_timeout = av_rescale_q(1, dda->time_base, (AVRational) { 1, 1000 }) / 2;
+
+ dda->last_frame = av_frame_alloc();
+ if (!dda->last_frame) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ dda->mouse_x = -1;
+ dda->mouse_y = -1;
+
+ if (dda->draw_mouse) {
+ ret = init_render_resources(avctx);
+ if (ret < 0)
+ goto fail;
+ }
+
+ return 0;
+fail:
+ ddagrab_uninit(avctx);
+ return ret;
+}
+
+static int create_d3d11_pointer_tex(AVFilterContext *avctx,
+ uint8_t *buf,
+ DXGI_OUTDUPL_POINTER_SHAPE_INFO *shape_info,
+ ID3D11Texture2D **out_tex,
+ ID3D11ShaderResourceView **res_view)
+{
+ DdagrabContext *dda = avctx->priv;
+ D3D11_TEXTURE2D_DESC desc = { 0 };
+ D3D11_SUBRESOURCE_DATA init_data = { 0 };
+ D3D11_SHADER_RESOURCE_VIEW_DESC resource_desc = { 0 };
+ HRESULT hr;
+
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ desc.SampleDesc.Count = 1;
+ desc.SampleDesc.Quality = 0;
+ desc.Usage = D3D11_USAGE_IMMUTABLE;
+ desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
+
+ desc.Width = shape_info->Width;
+ desc.Height = shape_info->Height;
+
+ init_data.pSysMem = buf;
+ init_data.SysMemPitch = shape_info->Pitch;
+
+ resource_desc.Format = desc.Format;
+ resource_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+ resource_desc.Texture2D.MostDetailedMip = 0;
+ resource_desc.Texture2D.MipLevels = 1;
+
+ hr = ID3D11Device_CreateTexture2D(dda->device_hwctx->device,
+ &desc,
+ &init_data,
+ out_tex);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "Failed creating pointer texture\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ hr = ID3D11Device_CreateShaderResourceView(dda->device_hwctx->device,
+ (ID3D11Resource*)dda->mouse_texture,
+ &resource_desc,
+ res_view);
+ if (FAILED(hr)) {
+ release_resource(out_tex);
+ av_log(avctx, AV_LOG_ERROR, "CreateShaderResourceView for mouse failed: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ return 0;
+}
+
+static uint8_t *convert_mono_buffer(uint8_t *input, int *_width, int *_height, int *_pitch)
+{
+ int width = *_width, height = *_height, pitch = *_pitch;
+ int real_height = height / 2;
+ uint8_t *output = av_malloc(real_height * width * 4);
+ int y, x;
+
+ if (!output)
+ return NULL;
+
+ // This simulates drawing the cursor on a full black surface
+ // i.e. ignore the AND mask, turn XOR mask into all 4 color channels
+ for (y = 0; y < real_height; y++) {
+ for (x = 0; x < width; x++) {
+ int v = input[(real_height + y) * pitch + (x / 8)];
+ v = (v >> (7 - (x % 8))) & 1;
+ memset(&output[4 * ((y*width) + x)], v ? 0xFF : 0, 4);
+ }
+ }
+
+ *_pitch = width * 4;
+ *_height = real_height;
+
+ return output;
+}
+
+static void fixup_color_mask(uint8_t *buf, int width, int height, int pitch)
+{
+ int x, y;
+ // There is no good way to replicate XOR'ig parts of the texture with the screen
+ // best effort is rendering the non-masked parts, and make the rest transparent
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ int pos = (y*pitch) + (4*x) + 3;
+ buf[pos] = buf[pos] ? 0 : 0xFF;
+ }
+ }
+}
+
+static int update_mouse_pointer(AVFilterContext *avctx, DXGI_OUTDUPL_FRAME_INFO *frame_info)
+{
+ DdagrabContext *dda = avctx->priv;
+ HRESULT hr;
+ int ret;
+
+ if (frame_info->LastMouseUpdateTime.QuadPart == 0)
+ return 0;
+
+ if (frame_info->PointerPosition.Visible) {
+ dda->mouse_x = frame_info->PointerPosition.Position.x;
+ dda->mouse_y = frame_info->PointerPosition.Position.y;
+ } else {
+ dda->mouse_x = dda->mouse_y = -1;
+ }
+
+ if (frame_info->PointerShapeBufferSize) {
+ UINT size = frame_info->PointerShapeBufferSize;
+ DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
+ uint8_t *buf = av_malloc(size);
+ if (!buf)
+ return AVERROR(ENOMEM);
+
+ hr = IDXGIOutputDuplication_GetFramePointerShape(dda->dxgi_outdupl,
+ size,
+ buf,
+ &size,
+ &shape_info);
+ if (FAILED(hr)) {
+ av_free(buf);
+ av_log(avctx, AV_LOG_ERROR, "Failed getting pointer shape: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ if (shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) {
+ uint8_t *new_buf = convert_mono_buffer(buf, &shape_info.Width, &shape_info.Height, &shape_info.Pitch);
+ av_free(buf);
+ if (!new_buf)
+ return AVERROR(ENOMEM);
+ buf = new_buf;
+ } else if (shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR) {
+ fixup_color_mask(buf, shape_info.Width, shape_info.Height, shape_info.Pitch);
+ } else if (shape_info.Type != DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR) {
+ av_log(avctx, AV_LOG_WARNING, "Unsupported pointer shape type: %d\n", (int)shape_info.Type);
+ av_free(buf);
+ return 0;
+ }
+
+ release_resource(&dda->mouse_resource_view);
+ release_resource(&dda->mouse_texture);
+
+ ret = create_d3d11_pointer_tex(avctx, buf, &shape_info, &dda->mouse_texture, &dda->mouse_resource_view);
+ av_freep(&buf);
+ if (ret < 0)
+ return ret;
+
+ av_log(avctx, AV_LOG_VERBOSE, "Updated pointer shape texture\n");
+ }
+
+ return 0;
+}
+
+static int next_frame_internal(AVFilterContext *avctx, ID3D11Texture2D **desktop_texture)
+{
+ DXGI_OUTDUPL_FRAME_INFO frame_info;
+ DdagrabContext *dda = avctx->priv;
+ IDXGIResource *desktop_resource = NULL;
+ HRESULT hr;
+ int ret;
+
+ hr = IDXGIOutputDuplication_AcquireNextFrame(
+ dda->dxgi_outdupl,
+ dda->time_timeout,
+ &frame_info,
+ &desktop_resource);
+ if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
+ return AVERROR(EAGAIN);
+ } else if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "AcquireNextFrame failed: %lx\n", hr);
+ return AVERROR_EXTERNAL;
+ }
+
+ if (dda->draw_mouse) {
+ ret = update_mouse_pointer(avctx, &frame_info);
+ if (ret < 0)
+ return ret;
+ }
+
+ hr = IDXGIResource_QueryInterface(desktop_resource, &IID_ID3D11Texture2D, (void**)desktop_texture);
+ IDXGIResource_Release(desktop_resource);
+ desktop_resource = NULL;
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "DXGIResource QueryInterface failed\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ return 0;
+}
+
+static int probe_output_format(AVFilterContext *avctx)
+{
+ DdagrabContext *dda = avctx->priv;
+ D3D11_TEXTURE2D_DESC desc;
+ int ret;
+
+ av_assert1(!dda->probed_texture);
+
+ do {
+ ret = next_frame_internal(avctx, &dda->probed_texture);
+ } while(ret == AVERROR(EAGAIN));
+ if (ret < 0)
+ return ret;
+
+ ID3D11Texture2D_GetDesc(dda->probed_texture, &desc);
+
+ dda->raw_format = desc.Format;
+ dda->raw_width = desc.Width;
+ dda->raw_height = desc.Height;
+
+ if (dda->width <= 0)
+ dda->width = dda->raw_width;
+ if (dda->height <= 0)
+ dda->height = dda->raw_height;
+
+ return 0;
+}
+
+static av_cold int init_hwframes_ctx(AVFilterContext *avctx)
+{
+ DdagrabContext *dda = avctx->priv;
+ int ret = 0;
+
+ dda->frames_ref = av_hwframe_ctx_alloc(dda->device_ref);
+ if (!dda->frames_ref)
+ return AVERROR(ENOMEM);
+ dda->frames_ctx = (AVHWFramesContext*)dda->frames_ref->data;
+ dda->frames_hwctx = (AVD3D11VAFramesContext*)dda->frames_ctx->hwctx;
+
+ dda->frames_ctx->format = AV_PIX_FMT_D3D11;
+ dda->frames_ctx->width = dda->width;
+ dda->frames_ctx->height = dda->height;
+
+ switch (dda->raw_format) {
+ case DXGI_FORMAT_B8G8R8A8_UNORM:
+ av_log(avctx, AV_LOG_VERBOSE, "Probed 8 bit RGB frame format\n");
+ dda->frames_ctx->sw_format = AV_PIX_FMT_BGRA;
+ break;
+ case DXGI_FORMAT_R10G10B10A2_UNORM:
+ av_log(avctx, AV_LOG_VERBOSE, "Probed 10 bit RGB frame format\n");
+ dda->frames_ctx->sw_format = AV_PIX_FMT_X2BGR10;
+ break;
+ default:
+ av_log(avctx, AV_LOG_ERROR, "Unexpected texture output format!\n");
+ return AVERROR_BUG;
+ }
+
+ if (dda->draw_mouse)
+ dda->frames_hwctx->BindFlags |= D3D11_BIND_RENDER_TARGET;
+
+ ret = av_hwframe_ctx_init(dda->frames_ref);
+ if (ret < 0) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to initialise hardware frames context: %d.\n", ret);
+ goto fail;
+ }
+
+ return 0;
+fail:
+ av_buffer_unref(&dda->frames_ref);
+ return ret;
+}
+
+static int ddagrab_config_props(AVFilterLink *outlink)
+{
+ AVFilterContext *avctx = outlink->src;
+ DdagrabContext *dda = avctx->priv;
+ int ret;
+
+ ret = probe_output_format(avctx);
+ if (ret < 0)
+ return ret;
+
+ dda->width -= FFMAX(dda->width - dda->raw_width + dda->offset_x, 0);
+ dda->height -= FFMAX(dda->height - dda->raw_height + dda->offset_y, 0);
+
+ ret = init_hwframes_ctx(avctx);
+ if (ret < 0)
+ return ret;
+
+ outlink->hw_frames_ctx = av_buffer_ref(dda->frames_ref);
+ if (!outlink->hw_frames_ctx)
+ return AVERROR(ENOMEM);
+
+ outlink->w = dda->width;
+ outlink->h = dda->height;
+ outlink->time_base = (AVRational){1, TIMER_RES};
+ outlink->frame_rate = dda->framerate;
+
+ return 0;
+}
+
+static int draw_mouse_pointer(AVFilterContext *avctx, AVFrame *frame)
+{
+ DdagrabContext *dda = avctx->priv;
+ ID3D11DeviceContext *devctx = dda->device_hwctx->device_context;
+ ID3D11Texture2D *frame_tex = (ID3D11Texture2D*)frame->data[0];
+ D3D11_RENDER_TARGET_VIEW_DESC target_desc = { 0 };
+ ID3D11RenderTargetView* target_view = NULL;
+ ID3D11Buffer *mouse_vertex_buffer = NULL;
+ D3D11_TEXTURE2D_DESC tex_desc;
+ int num_vertices = 0;
+ int x, y;
+ HRESULT hr;
+ int ret = 0;
+
+ if (!dda->mouse_texture || dda->mouse_x < 0 || dda->mouse_y < 0)
+ return 0;
+
+ ID3D11Texture2D_GetDesc(dda->mouse_texture, &tex_desc);
+
+ x = dda->mouse_x - dda->offset_x;
+ y = dda->mouse_y - dda->offset_y;
+
+ if (x >= dda->width || y >= dda->height ||
+ -x >= (int)tex_desc.Width || -y >= (int)tex_desc.Height)
+ return 0;
+
+ target_desc.Format = dda->raw_format;
+ target_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+ target_desc.Texture2D.MipSlice = 0;
+
+ hr = ID3D11Device_CreateRenderTargetView(dda->device_hwctx->device,
+ (ID3D11Resource*)frame_tex,
+ &target_desc,
+ &target_view);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "CreateRenderTargetView failed: %lx\n", hr);
+ ret = AVERROR_EXTERNAL;
+ goto end;
+ }
+
+ ID3D11DeviceContext_ClearState(devctx);
+
+ {
+ D3D11_VIEWPORT viewport = { 0 };
+ viewport.Width = dda->width;
+ viewport.Height = dda->height;
+ viewport.MinDepth = 0.0f;
+ viewport.MaxDepth = 1.0f;
+
+ ID3D11DeviceContext_RSSetViewports(devctx, 1, &viewport);
+ }
+
+ {
+ FLOAT vertices[] = {
+ // x, y, z, u, v
+ x , y + tex_desc.Height, 0.0f, 0.0f, 1.0f,
+ x , y , 0.0f, 0.0f, 0.0f,
+ x + tex_desc.Width, y + tex_desc.Height, 0.0f, 1.0f, 1.0f,
+ x + tex_desc.Width, y , 0.0f, 1.0f, 0.0f,
+ };
+ UINT stride = sizeof(FLOAT) * 5;
+ UINT offset = 0;
+
+ D3D11_SUBRESOURCE_DATA init_data = { 0 };
+ D3D11_BUFFER_DESC buf_desc = { 0 };
+
+ num_vertices = sizeof(vertices) / (sizeof(FLOAT) * 5);
+
+ buf_desc.Usage = D3D11_USAGE_DEFAULT;
+ buf_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
+ buf_desc.ByteWidth = sizeof(vertices);
+ init_data.pSysMem = vertices;
+
+ hr = ID3D11Device_CreateBuffer(dda->device_hwctx->device,
+ &buf_desc,
+ &init_data,
+ &mouse_vertex_buffer);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "CreateBuffer failed: %lx\n", hr);
+ ret = AVERROR_EXTERNAL;
+ goto end;
+ }
+
+ ID3D11DeviceContext_IASetVertexBuffers(devctx, 0, 1, &mouse_vertex_buffer, &stride, &offset);
+ ID3D11DeviceContext_IASetInputLayout(devctx, dda->input_layout);
+ ID3D11DeviceContext_IASetPrimitiveTopology(devctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
+ }
+
+ ID3D11DeviceContext_VSSetShader(devctx, dda->vertex_shader, NULL, 0);
+ ID3D11DeviceContext_VSSetConstantBuffers(devctx, 0, 1, &dda->const_buffer);
+ ID3D11DeviceContext_PSSetSamplers(devctx, 0, 1, &dda->sampler_state);
+ ID3D11DeviceContext_PSSetShaderResources(devctx, 0, 1, &dda->mouse_resource_view);
+ ID3D11DeviceContext_PSSetShader(devctx, dda->pixel_shader, NULL, 0);
+
+ ID3D11DeviceContext_OMSetBlendState(devctx, dda->blend_state, NULL, 0xFFFFFFFF);
+ ID3D11DeviceContext_OMSetRenderTargets(devctx, 1, &target_view, NULL);
+
+ ID3D11DeviceContext_Draw(devctx, num_vertices, 0);
+
+end:
+ release_resource(&mouse_vertex_buffer);
+ release_resource(&target_view);
+
+ return ret;
+}
+
+static int ddagrab_request_frame(AVFilterLink *outlink)
+{
+ AVFilterContext *avctx = outlink->src;
+ DdagrabContext *dda = avctx->priv;
+
+ ID3D11Texture2D *cur_texture = NULL;
+ D3D11_TEXTURE2D_DESC desc = { 0 };
+ D3D11_BOX box = { 0 };
+
+ int64_t time_frame = dda->time_frame;
+ int64_t now, delay;
+ AVFrame *frame = NULL;
+ HRESULT hr;
+ int ret;
+
+ /* time_frame is in units of microseconds divided by the time_base.
+ * This means that adding a clean 1M to it is the equivalent of adding
+ * 1M*time_base microseconds to it, except it avoids all rounding error.
+ * The only time rounding error occurs is when multiplying to calculate
+ * the delay. So any rounding error there corrects itself over time.
+ */
+ time_frame += TIMER_RES64;
+ for (;;) {
+ now = av_gettime_relative();
+ delay = time_frame * av_q2d(dda->time_base) - now;
+ if (delay <= 0) {
+ if (delay < -TIMER_RES64 * av_q2d(dda->time_base)) {
+ time_frame += TIMER_RES64;
+ }
+ break;
+ }
+ av_usleep(delay);
+ }
+
+ if (!dda->first_pts)
+ dda->first_pts = now;
+ now -= dda->first_pts;
+
+ if (!dda->probed_texture) {
+ ret = next_frame_internal(avctx, &cur_texture);
+ } else {
+ cur_texture = dda->probed_texture;
+ dda->probed_texture = NULL;
+ ret = 0;
+ }
+
+ if (ret == AVERROR(EAGAIN) && dda->last_frame->buf[0]) {
+ frame = av_frame_alloc();
+ if (!frame)
+ return AVERROR(ENOMEM);
+
+ ret = av_frame_ref(frame, dda->last_frame);
+ if (ret < 0) {
+ av_frame_free(&frame);
+ return ret;
+ }
+
+ av_log(avctx, AV_LOG_DEBUG, "Duplicated output frame\n");
+
+ goto frame_done;
+ } else if (ret == AVERROR(EAGAIN)) {
+ av_log(avctx, AV_LOG_VERBOSE, "Initial DDA AcquireNextFrame timeout!\n");
+ return AVERROR(EAGAIN);
+ } else if (ret < 0) {
+ return ret;
+ }
+
+ // AcquireNextFrame sometimes has bursts of delay.
+ // This increases accuracy of the timestamp, but might upset consumers due to more jittery framerate?
+ now = av_gettime_relative() - dda->first_pts;
+
+ ID3D11Texture2D_GetDesc(cur_texture, &desc);
+ if (desc.Format != dda->raw_format ||
+ (int)desc.Width != dda->raw_width ||
+ (int)desc.Height != dda->raw_height) {
+ av_log(avctx, AV_LOG_ERROR, "Output parameters changed!");
+ ret = AVERROR_OUTPUT_CHANGED;
+ goto fail;
+ }
+
+ frame = ff_get_video_buffer(outlink, dda->width, dda->height);
+ if (!frame) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ box.left = dda->offset_x;
+ box.top = dda->offset_y;
+ box.right = box.left + dda->width;
+ box.bottom = box.top + dda->height;
+ box.front = 0;
+ box.back = 1;
+
+ ID3D11DeviceContext_CopySubresourceRegion(
+ dda->device_hwctx->device_context,
+ (ID3D11Resource*)frame->data[0], (UINT)(intptr_t)frame->data[1],
+ 0, 0, 0,
+ (ID3D11Resource*)cur_texture, 0,
+ &box);
+
+ release_resource(&cur_texture);
+
+ hr = IDXGIOutputDuplication_ReleaseFrame(dda->dxgi_outdupl);
+ if (FAILED(hr)) {
+ av_log(avctx, AV_LOG_ERROR, "DDA ReleaseFrame failed!\n");
+ ret = AVERROR_EXTERNAL;
+ goto fail;
+ }
+
+ if (dda->draw_mouse) {
+ ret = draw_mouse_pointer(avctx, frame);
+ if (ret < 0)
+ goto fail;
+ }
+
+ frame->sample_aspect_ratio = (AVRational){1, 1};
+
+ av_frame_unref(dda->last_frame);
+ ret = av_frame_ref(dda->last_frame, frame);
+ if (ret < 0)
+ return ret;
+
+frame_done:
+ frame->pts = now;
+ dda->time_frame = time_frame;
+
+ return ff_filter_frame(outlink, frame);
+
+fail:
+ if (frame)
+ av_frame_free(&frame);
+
+ if (cur_texture)
+ IDXGIOutputDuplication_ReleaseFrame(dda->dxgi_outdupl);
+
+ release_resource(&cur_texture);
+ return ret;
+}
+
+static const AVFilterPad ddagrab_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .request_frame = ddagrab_request_frame,
+ .config_props = ddagrab_config_props,
+ },
+};
+
+const AVFilter ff_vsrc_ddagrab = {
+ .name = "ddagrab",
+ .description = NULL_IF_CONFIG_SMALL("Grab Windows Desktop images using Desktop Duplication API"),
+ .priv_size = sizeof(DdagrabContext),
+ .priv_class = &ddagrab_class,
+ .init = ddagrab_init,
+ .uninit = ddagrab_uninit,
+ .inputs = NULL,
+ FILTER_OUTPUTS(ddagrab_outputs),
+ FILTER_SINGLE_PIXFMT(AV_PIX_FMT_D3D11),
+ .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
+};