我的应用程序的用户在用DesktopDuplication API
捕获屏幕时遇到一些问题。
在开始捕获时,由于应用程序无法释放OutputDuplication
的帧而导致应用程序崩溃。
用户的PC详细信息:
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
,它可以正常工作。我仍在分析文件。
我的应用程序的用户在使用DesktopDuplication API捕获屏幕时遇到了一些问题。开始捕获后,应用程序崩溃,因为该应用程序无法释放......>
ReleaseFrame()
。IMO,您应该检查的唯一重要的是DXGI_ERROR_ACCESS_LOST
,因为在这种情况下,您需要一个新的IDXGIOutputDuplication
实例(我尚未在代码中执行此操作)。通过执行此操作,我无法衡量任何性能损失。