在屏幕捕获期间检测帧丢失
Detecting frame drops during screen capture
我正在使用 SharpDx 捕捉屏幕(1 到 60fps)。有些帧都是透明的,最终被代码处理和保存。
是否有任何 simple/fast 方法可以检测这些掉帧而无需打开生成的位图并查找 alpha 值?
这是我正在使用的(将捕获保存为图像):
try
{
//Try to get duplicated frame within given time.
_duplicatedOutput.AcquireNextFrame(MinimumDelay, out var duplicateFrameInformation, out var screenResource);
//Copy resource into memory that can be accessed by the CPU.
using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
_device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, new ResourceRegion(Left, Top, 0, Left + Width, Top + Height, 1), _screenTexture, 0);
//Get the desktop capture texture.
var mapSource = _device.ImmediateContext.MapSubresource(_screenTexture, 0, MapMode.Read, MapFlags.None); //, out var stream);
#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 GDI bitmap.
var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
var sourcePtr = mapSource.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, mapSource.RowPitch);
destPtr = IntPtr.Add(destPtr, mapDest.Stride);
}
//Release source and dest locks
bitmap.UnlockBits(mapDest);
//Bitmap is saved in here!!!
#endregion
_device.ImmediateContext.UnmapSubresource(_screenTexture, 0);
screenResource.Dispose();
_duplicatedOutput.ReleaseFrame();
}
catch (SharpDXException e)
{
if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
throw;
}
这是一个modified version from this one。
我也有这个版本(将捕获保存为像素阵列):
//Get the desktop capture texture.
var data = _device.ImmediateContext.MapSubresource(_screenTexture, 0, MapMode.Read, MapFlags.None, out var stream);
var bytes = new byte[stream.Length];
//BGRA32 is 4 bytes.
for (var height = 0; height < Height; height++)
{
stream.Position = height * data.RowPitch;
Marshal.Copy(new IntPtr(stream.DataPointer.ToInt64() + height * data.RowPitch), bytes, height * Width * 4, Width * 4);
}
我不确定这是否是将屏幕截图另存为图像 and/or 像素阵列的最佳方式,但它有点管用。
不管怎样,问题是有些捕获的帧是完全透明的,对我来说没用。我需要以某种方式完全避免保存它们。
当捕获为像素数组时,我可以简单地检查 bytes
数组,知道第 4 项是 255 还是 0。当保存为图像时,我可以使用 bitmap.GetPixel(0,0).A
知道图片是否有内容
但是对于这两种方式,我都需要完成捕获并获取完整的图像内容,然后才能知道帧是否丢失。
有什么方法可以知道帧是否被正确捕获?
您的问题归结为您试图在计时器上执行此操作。无法保证每个 tick/frame 的 最小 执行时间。如果用户选择的值太高,就会出现这样的效果。在最坏的情况下,您可能会在 EventQueue 中排队,直到您 运行 进入异常,因为队列已满。
您需要做的是将速率限制在最大值。如果它不能像用户想要的那样快速运行,那就是现实。我为这种情况写了一些简单的速率限制代码:
integer interval = 20;
DateTime dueTime = DateTime.Now.AddMillisconds(interval);
while(true){
if(DateTime.Now >= dueTime){
//insert code here
//Update next dueTime
dueTime = DateTime.Now.AddMillisconds(interval);
}
else{
//Just yield to not tax out the CPU
Thread.Sleep(1);
}
}
只有两个注释:
- 这是为了 运行 在单独的线程中设计的。您必须 运行 如此,或者调整 Thread.Sleep() 以适应您选择的多任务处理选项。
- DateTime.Now 不适合这么小的时间范围(2 位数毫秒)。通常返回值只会每 18 毫秒左右更新一次。它实际上随时间变化。 60 FPS 让你大约 16 毫秒。你应该使用秒表或类似的东西。
为了忽略丢帧,我使用了这段代码(到目前为止它按预期工作):
//Try to get the duplicated frame within given time.
_duplicatedOutput.AcquireNextFrame(1000, out var duplicateFrameInformation, out var screenResource);
//Somehow, it was not possible to retrieve the resource.
if (screenResource == null || duplicateFrameInformation.AccumulatedFrames == 0)
{
//Mark the frame as dropped.
frame.WasDropped = true;
FrameList.Add(frame);
screenResource?.Dispose();
_duplicatedOutput.ReleaseFrame();
return;
}
我只是检查 screenResource
是否不为空以及是否有帧累积。
我正在使用 SharpDx 捕捉屏幕(1 到 60fps)。有些帧都是透明的,最终被代码处理和保存。
是否有任何 simple/fast 方法可以检测这些掉帧而无需打开生成的位图并查找 alpha 值?
这是我正在使用的(将捕获保存为图像):
try
{
//Try to get duplicated frame within given time.
_duplicatedOutput.AcquireNextFrame(MinimumDelay, out var duplicateFrameInformation, out var screenResource);
//Copy resource into memory that can be accessed by the CPU.
using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
_device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, new ResourceRegion(Left, Top, 0, Left + Width, Top + Height, 1), _screenTexture, 0);
//Get the desktop capture texture.
var mapSource = _device.ImmediateContext.MapSubresource(_screenTexture, 0, MapMode.Read, MapFlags.None); //, out var stream);
#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 GDI bitmap.
var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
var sourcePtr = mapSource.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, mapSource.RowPitch);
destPtr = IntPtr.Add(destPtr, mapDest.Stride);
}
//Release source and dest locks
bitmap.UnlockBits(mapDest);
//Bitmap is saved in here!!!
#endregion
_device.ImmediateContext.UnmapSubresource(_screenTexture, 0);
screenResource.Dispose();
_duplicatedOutput.ReleaseFrame();
}
catch (SharpDXException e)
{
if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
throw;
}
这是一个modified version from this one。
我也有这个版本(将捕获保存为像素阵列):
//Get the desktop capture texture.
var data = _device.ImmediateContext.MapSubresource(_screenTexture, 0, MapMode.Read, MapFlags.None, out var stream);
var bytes = new byte[stream.Length];
//BGRA32 is 4 bytes.
for (var height = 0; height < Height; height++)
{
stream.Position = height * data.RowPitch;
Marshal.Copy(new IntPtr(stream.DataPointer.ToInt64() + height * data.RowPitch), bytes, height * Width * 4, Width * 4);
}
我不确定这是否是将屏幕截图另存为图像 and/or 像素阵列的最佳方式,但它有点管用。
不管怎样,问题是有些捕获的帧是完全透明的,对我来说没用。我需要以某种方式完全避免保存它们。
当捕获为像素数组时,我可以简单地检查 bytes
数组,知道第 4 项是 255 还是 0。当保存为图像时,我可以使用 bitmap.GetPixel(0,0).A
知道图片是否有内容
但是对于这两种方式,我都需要完成捕获并获取完整的图像内容,然后才能知道帧是否丢失。
有什么方法可以知道帧是否被正确捕获?
您的问题归结为您试图在计时器上执行此操作。无法保证每个 tick/frame 的 最小 执行时间。如果用户选择的值太高,就会出现这样的效果。在最坏的情况下,您可能会在 EventQueue 中排队,直到您 运行 进入异常,因为队列已满。
您需要做的是将速率限制在最大值。如果它不能像用户想要的那样快速运行,那就是现实。我为这种情况写了一些简单的速率限制代码:
integer interval = 20;
DateTime dueTime = DateTime.Now.AddMillisconds(interval);
while(true){
if(DateTime.Now >= dueTime){
//insert code here
//Update next dueTime
dueTime = DateTime.Now.AddMillisconds(interval);
}
else{
//Just yield to not tax out the CPU
Thread.Sleep(1);
}
}
只有两个注释:
- 这是为了 运行 在单独的线程中设计的。您必须 运行 如此,或者调整 Thread.Sleep() 以适应您选择的多任务处理选项。
- DateTime.Now 不适合这么小的时间范围(2 位数毫秒)。通常返回值只会每 18 毫秒左右更新一次。它实际上随时间变化。 60 FPS 让你大约 16 毫秒。你应该使用秒表或类似的东西。
为了忽略丢帧,我使用了这段代码(到目前为止它按预期工作):
//Try to get the duplicated frame within given time.
_duplicatedOutput.AcquireNextFrame(1000, out var duplicateFrameInformation, out var screenResource);
//Somehow, it was not possible to retrieve the resource.
if (screenResource == null || duplicateFrameInformation.AccumulatedFrames == 0)
{
//Mark the frame as dropped.
frame.WasDropped = true;
FrameList.Add(frame);
screenResource?.Dispose();
_duplicatedOutput.ReleaseFrame();
return;
}
我只是检查 screenResource
是否不为空以及是否有帧累积。