c# - DesktopDuplication API 中的 ReleaseFrame() 调用无效

标签 c# sharpdx desktop-duplication

我的应用程序的用户在使用 DesktopDuplication API 捕获屏幕时遇到问题.

开始捕获后,应用程序崩溃,因为应用程序无法释放 OutputDuplication 的帧.

用户电脑详情:

Windows 10.0.18362.0, 64 bits
Nvidia GeForce GTX 960, driver version 441.66

错误日志:
▬ Message - 
HRESULT: [0x887A0001], Module: [SharpDX.DXGI], ApiCode: [DXGI_ERROR_INVALID_CALL/InvalidCall], 
Message: The application made a call that is invalid. Either the parameters of the call or the state of some object was incorrect.
Enable the D3D debug layer in order to see details via debug messages.

○ Type - 
    SharpDX.SharpDXException
▲ Source - 
    SharpDX
▼ TargetSite - 
    Void CheckError()
♠ StackTrace - 
   at SharpDX.Result.CheckError()
   at SharpDX.DXGI.OutputDuplication.ReleaseFrame()
   at MyApp.Capture.DirectImageCapture.CaptureWithCursor(FrameInfo frame)

----------------------------------

▬ Message - 
    Object reference not set to an instance of an object.
○ Type - 
    System.NullReferenceException
▲ Source - 
    SharpDX.Direct3D11
▼ TargetSite - 
    Void GetDescription(SharpDX.Direct3D11.Texture2DDescription ByRef)
♠ StackTrace - 
   at SharpDX.Direct3D11.Texture2D.GetDescription(Texture2DDescription& descRef)
   at MyApp.Capture.DirectImageCapture.GetCursor(Texture2D screenTexture, OutputDuplicateFrameInformation info, FrameInfo frame)
   at MyApp.Capture.DirectImageCapture.CaptureWithCursor(FrameInfo frame)

用户可以在没有捕获鼠标光标的情况下捕获屏幕,因此捕获光标的捕获方法一定有问题。
    var res = Result.Ok;

    try
    {
        //Try to get the duplicated output frame within given time.
        res = DuplicatedOutput.TryAcquireNextFrame(0, out var info, out var resource);

        //Checks how to proceed with the capture. It could have failed, or the screen, cursor or both could have been captured.
        if (res.Failure || resource == null || (info.AccumulatedFrames == 0 && info.LastMouseUpdateTime <= LastProcessTime))
        {
            //Somehow, it was not possible to retrieve the resource, frame or metadata.
            //frame.WasDropped = true;
            //BlockingCollection.Add(frame);

            resource?.Dispose();
            return FrameCount;
        }
        else if (info.AccumulatedFrames == 0 && info.LastMouseUpdateTime > LastProcessTime)
        {
            //Gets the cursor shape if the screen hasn't changed in between, so the cursor will be available for the next frame.
            GetCursor(null, info, frame);
            return FrameCount;
        }

        //Saves the most recent capture time.
        LastProcessTime = Math.Max(info.LastPresentTime, info.LastMouseUpdateTime);

        //Copy resource into memory that can be accessed by the CPU.
        using (var screenTexture = resource.QueryInterface<Texture2D>())
        {
            //Copies from the screen texture only the area which the user wants to capture.
            Device.ImmediateContext.CopySubresourceRegion(screenTexture, 0, new ResourceRegion(TrueLeft, TrueTop, 0, TrueRight, TrueBottom, 1), BackingTexture, 0);

            //Copy the captured desktop texture into a staging texture, in order to show the mouse cursor and not make the captured texture dirty with it.
            Device.ImmediateContext.CopyResource(BackingTexture, StagingTexture);

            //Gets the cursor image and merges with the staging texture.
            GetCursor(StagingTexture, info, frame);
        }

        //Get the desktop capture texture.
        var data = Device.ImmediateContext.MapSubresource(StagingTexture, 0, MapMode.Read, MapFlags.None);

        if (data.IsEmpty)
        {
            //frame.WasDropped = true;
            //BlockingCollection.Add(frame);

            Device.ImmediateContext.UnmapSubresource(StagingTexture, 0);
            resource.Dispose();
            return FrameCount;
        }

        #region Get image data

        var bitmap = new System.Drawing.Bitmap(Width, Height, PixelFormat.Format32bppArgb);
        var boundsRect = new System.Drawing.Rectangle(0, 0, Width, Height);

        //Copy pixels from screen capture Texture to the GDI bitmap.
        var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
        var sourcePtr = data.DataPointer;
        var destPtr = mapDest.Scan0;

        for (var y = 0; y < Height; y++)
        {
            //Copy a single line.
            Utilities.CopyMemory(destPtr, sourcePtr, Width * 4);

            //Advance pointers.
            sourcePtr = IntPtr.Add(sourcePtr, data.RowPitch);
            destPtr = IntPtr.Add(destPtr, mapDest.Stride);
        }

        //Release source and dest locks.
        bitmap.UnlockBits(mapDest);

        //Set frame details.
        FrameCount++;
        frame.Path = $"{Project.FullPath}{FrameCount}.png";
        frame.Delay = FrameRate.GetMilliseconds(SnapDelay);
        frame.Image = bitmap;
        BlockingCollection.Add(frame);

        #endregion

        Device.ImmediateContext.UnmapSubresource(StagingTexture, 0);

        resource.Dispose();
        return FrameCount;
    }
    catch (SharpDXException se) when (se.ResultCode.Code == SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
    {
        return FrameCount;
    }
    catch (SharpDXException se) when (se.ResultCode.Code == SharpDX.DXGI.ResultCode.DeviceRemoved.Result.Code || se.ResultCode.Code == SharpDX.DXGI.ResultCode.DeviceReset.Result.Code)
    {
        //When the device gets lost or reset, the resources should be instantiated again.
        DisposeInternal();
        Initialize();

        return FrameCount;
    }
    catch (Exception ex)
    {
        LogWriter.Log(ex, "It was not possible to finish capturing the frame with DirectX.");

        OnError.Invoke(ex);
        return FrameCount;
    }
    finally
    {
        try
        {
            //Only release the frame if there was a success in capturing it.
            if (res.Success)
                DuplicatedOutput.ReleaseFrame();
        }
        catch (Exception e)
        {
            LogWriter.Log(e, "It was not possible to release the frame.");

            //HERE
            //What should I do after the frame is not released properly?
            //Should I reset the whole capture?
            //DisposeInternal();
            //Initialize();
        }
    }

当每帧的捕获完成时,DuplicatedOutput必须释放框架。

但是当发布失败时 InvalidCall , 我该怎么办?
另外,如何在用户 PC 上调试这种错误(而不是在开发人员的机器上)?

编辑:

这就是我想要做的:

Graphics ToolsWindows Settings 上启用,我将此代码添加到捕获初始化中:
#if DEBUG
    Device = new Device(DriverType.Hardware, DeviceCreationFlags.VideoSupport | DeviceCreationFlags.Debug);

    var debug = InfoQueue.TryCreate();
    debug.SetBreakOnSeverity(DebugId.All, InformationQueueMessageSeverity.Corruption, true);
    debug.SetBreakOnSeverity(DebugId.All, InformationQueueMessageSeverity.Error, true);
    debug.SetBreakOnSeverity(DebugId.All, InformationQueueMessageSeverity.Warning, true);

    var debug2 = DXGIDebug.TryCreate();
    debug2.ReportLiveObjects(DebugId.Dx, DebugRloFlags.Summary | DebugRloFlags.Detail);

#else

然后我将应用程序设置为在笔记本电脑的专用 GPU 上运行,因为我确信它会导致 InvalidException .

我运行应用程序并尝试 output1.DuplicateOutput(Device);它按预期失败了。

之后,我尝试在 DebugView 也在运行时运行该应用程序,它只在关闭应用程序时给我一些消息,而不是在出现错误时给我一些消息。
00000001    0.00000000  [14488] OnFocusWindowChanged to Lizard Mode 
00000002    0.39583239  [14488] Lizard Mode: Unprivileged process   
00000003    0.39594769  [14488] Lizard Mode: Restoring app mapping  
00000004    9.81729603  [21620] D3D11 WARNING: Process is terminating. Using simple reporting. Please call ReportLiveObjects() at runtime for standard reporting. [ STATE_CREATION WARNING #0: UNKNOWN] 
00000005    9.81732273  [21620] D3D11: **BREAK** enabled for the previous message, which was: [ WARNING STATE_CREATION #0: UNKNOWN ]    
00000006    9.81803799  [21620] DXGI WARNING: Process is terminating. Using simple reporting. Please call ReportLiveObjects() at runtime for standard reporting. [ STATE_CREATION WARNING #0: ] 
00000007    9.81806469  [21620] DXGI: **BREAK** enabled for the previous message, which was: [ WARNING STATE_CREATION #0:  ]    
00000008    10.78524113 [14488] Lizard Mode: Privileged process 
00000009    10.78589630 [14488] Lizard Mode: Reverting to default M/KB Configuration    
00000010    10.78692913 [14488] OnFocusWindowChanged to Lizard Mode 

因此,我尝试使用 dxcap 捕获错误。 ,使用此命令:
dxcap -debug -toXML '[path]\debug.xml' -file '[path]\debug.vsglog' -c '[path]\bin\Debug\MyApp.exe'
不幸的是,CreateDevice()失败:

HRESULT: [0x887A0004], Module: [SharpDX.DXGI], ApiCode: [DXGI_ERROR_UNSUPPORTED/Unsupported], Message: The specified device interface or feature level is not supported on this system.



然后我又试了一次,但这次只有 DeviceCreationFlags.Debug它奏效了。我还在分析文件。

Debug

最佳答案

好像是 we forked the same code对于 C# 桌面复制,我在释放框架时也遇到了一些问题。我对其进行了一些修改,目前我的(仍然不完整的)流光溢彩应用程序没有任何问题。您可以在这里查看代码:https://github.com/leocb/Ambilight
随意复制您喜欢的代码的任何部分。与您相关的部分在 DesktopDuplication 项目中,同时检查我在 ConfigForm.cs 上使用的功能

我忽略任何错误 ReleaseFrame()可以通过将其放入空白的 try/catch 中来抛出。
IMO,您应该检查的唯一重要的是DXGI_ERROR_ACCESS_LOST ,因为在这种情况下,您需要一个新的 IDXGIOutputDuplication实例(我还没有在我的代码上这样做)。我无法通过这样做来衡量任何性能损失。

确保正确初始化 SharpDX(设备、工厂等)并为表面纹理设置正确的标志

我建议对 GDI 图像使用双缓冲区,这样可以避免竞争条件错误并提高性能

此外,出于性能原因,我在另一个线程上执行捕获代码,我只调用 ReleaseFrame()在进行另一次捕获之前,如 recommended by microsoft :

[...] we recommend that you release the frame just before you call the IDXGIOutputDuplication::AcquireNextFrame method to acquire the next frame. When the client does not own the frame, the operating system copies all desktop updates to the surface.



如果这一切都失败了,“更简单”的解决方案是使用标准 c# 代码捕获鼠标位置并将其绘制在屏幕截图的顶部

关于c# - DesktopDuplication API 中的 ReleaseFrame() 调用无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60198426/

相关文章:

c++ - DirectX 屏幕捕获 - 桌面复制 API - AcquireNextFrame 的帧速率有限

c# - 使用 dropdownlist 将新数据添加到多个表

c# - 如何从 Global.asax 呈现一个 asp.net WebForm 页面?

c# - 在 SharpDX/DirectWrite 中获取 ABC 字符宽度

c# - SharpDX v4.0.1 sharpdx_direct3d11_effects_x64.dll DLLNotFoundException

video-capture - 使用 Microsoft Media Foundation 和 Desktop Duplication API 创建视频

c++ - 链接 Cuda (cudart.lib) 使 DXGI DuplicateOutput1() 失败

c# - ExecuteStoreQuery 将参数传递给 SQL 语句

c# - ConcurrentDictionary - 损坏的字典或错误的代码?

c# - 设置透视图的缩放值等于透视图