我的应用程序的一个用户在用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 Tools
上启用Windows 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,模块: SharpDX.DXGI,ApiCode: DXGI_ERROR_Unsupported/不支持,消息:此系统不支持指定的设备接口或功能级别。
然后我又试了一次,但这次只使用DeviceCreationFlags.Debug
,它成功了。我还在分析文件。
发布于 2020-03-04 14:00:28
似乎我们分叉了相同的代码用于C#桌面复制,我在发布框架时也遇到了一些问题。我对它做了一些修改,目前我的ambilight应用程序(还不完整)没有任何问题。您可以在这里查看代码:https://github.com/leocb/Ambilight可以随意复制您喜欢的代码的任何部分。您的相关部分在DesktopDuplication项目中,也检查我在ConfigForm.cs
上使用的函数。
我忽略了ReleaseFrame()
可能通过将其放入一个空白的try/catch中而引发的任何错误。IMO,您应该检查的唯一重要的是DXGI_ERROR_ACCESS_LOST
,因为在本例中,您需要一个新的IDXGIOutputDuplication
实例(我还没有在我的代码上这样做)。我无法通过这样做来衡量任何性能损失。
确保您正在正确初始化SharpDX (设备、工厂等)并为表面纹理设置正确的标志。
我建议对GDI映像使用双缓冲区,这样可以避免争用条件错误并提高性能。
此外,出于性能原因,我在另一个线程上执行捕获代码,并且只在执行另一个捕获之前调用ReleaseFrame()
,即微软推荐。
..。我们建议在调用IDXGIOutputDuplication::AcquireNextFrame方法获取下一个框架之前释放该框架。当客户端不拥有框架时,操作系统会将所有桌面更新复制到桌面上。
如果这一切都失败了,一个“更简单的”解决方案是用标准的c#代码捕获鼠标位置,并在屏幕截图上方绘制鼠标位置。
https://stackoverflow.com/questions/60198426
复制相似问题