在桌面复制中随机出现 AcquireNextFrame() 和 ReleaseFrame() 死锁 API
Deadlock on AcquireNextFrame() and ReleaseFrame() randomly in Desktop Duplication API
我正在尝试编写用于将 Windows 桌面录制到视频文件的应用程序。我正在使用桌面复制 API 和媒体基础 API。更具体地说,我使用的是 Media Foundation 的 SinkWriter
class。我正在使用桌面复制 API 中的 Texture2D
个对象并将它们传递给 SinkWriter
,这形成了一个很好的硬件加速管道。我也在使用 C# SharpDX
.
整个项目大部分都运行良好。它在我的 Surface Pro 上运行完美,捕捉相当一致的 fps 视频没有问题。
问题出在其他硬件上。具体来说,我已经测试过我已经在带有独立 Nvidia GPU 的 AMD Ryzen 上对此进行了测试。这也相当有效.. 除了通常在 8 到 20 秒之间记录桌面后,程序将被锁定在 outputDuplication.ReleaseFrame()
或 outputDuplication.AcquireNextFrame()
。它不会崩溃,它只是锁定了,所以我不确定如何弄清楚发生了什么。我确实知道它与将桌面纹理传递给 sinkWriter.WriteSample()
函数有关。如果我删除此行,并保持其他所有内容不变,就像往常一样继续创建 MediaBuffers
和 MediaSamples
,将它们与我的桌面纹理相关联,但不要将示例传递给WriteSample
方法,然后应用程序可以愉快地"record"看似无限期。
我检查了所有我能想到的潜在错误。我确保所有的对象都在它们应该被处理的时候被处理掉。我看不到内存泄漏。该代码在 MediaSample
上使用 SetAllocator()
方法,因此我传递给 SinkWriter
的纹理被回收。据我所知,这一切工作正常。
我怀疑这里有一些我不理解的基本知识。感觉我在正确管理 SharpDX
背后的非托管资源方面犯了一些错误,但我真的不知道从哪里开始看。此时将不胜感激任何指点。
这是主要的捕获代码。这不是全部,而是大部分。我怀疑问题出在这里。
static void WriteFrame(SinkWriter sinkWriter, int steamIndex, long time, long duration, int width, int height, Texture2D texture, TextureAllocator textureAllocator)
{
MediaBuffer textureBuffer;
MediaFactory.CreateDXGISurfaceBuffer(texture.GetType().GUID, texture, 0, false, out textureBuffer);
using (var buffer2d = textureBuffer.QueryInterface<Buffer2D>())
{
textureBuffer.CurrentLength = buffer2d.ContiguousLength;
}
using (var sample = MediaFactory.CreateVideoSampleFromSurface(null))
{
sample.SampleTime = time;
sample.SampleDuration = duration;
sample.AddBuffer(textureBuffer);
using(var trackedSample = sample.QueryInterface<TrackedSample>())
{
trackedSample.SetAllocator(textureAllocator, null);
}
sinkWriter.WriteSample(steamIndex, sample);
}
textureBuffer.Dispose();
}
void captureAction()
{
MediaManager.Startup(true);
int streamIndex = 0;
var whichOutputDevice = 0;
var whichAdapter = 0;
using (var factory = new Factory1())
{
using (var adapter = factory.GetAdapter1(whichAdapter))
{
using (var mDevice = new SharpDX.Direct3D11.Device(adapter))
{
using (var dXGIDeviceManager = new DXGIDeviceManager())
{
dXGIDeviceManager.ResetDevice(mDevice);
using (var output = adapter.GetOutput(whichOutputDevice))
{
using (var output1 = output.QueryInterface<Output1>())
{
var mOutputDesc = output.Description;
var mTextureDesc = new Texture2DDescription()
{
CpuAccessFlags = CpuAccessFlags.Read,
BindFlags = BindFlags.None,
Format = Format.B8G8R8A8_UNorm,
Width = mOutputDesc.DesktopBounds.Right - mOutputDesc.DesktopBounds.Left,
Height = mOutputDesc.DesktopBounds.Bottom - mOutputDesc.DesktopBounds.Top,
OptionFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Staging
};
using (var outputDuplication = output1.DuplicateOutput(mDevice))
{
using (var sinkWriter = InitialiseSinkWriter(path, out streamIndex, mTextureDesc.Width, mTextureDesc.Height, frameRate, mDevice, dXGIDeviceManager, preferHardwareAcceleration))
{
using (var rgbToNv12 = new RGBToNV12ConverterD3D11(mDevice, mDevice.ImmediateContext, mTextureDesc.Width, mTextureDesc.Height))
{
var newDesc = mTextureDesc;
if (forceNV12)
newDesc.Format = Format.NV12;
newDesc.BindFlags = BindFlags.RenderTarget;
newDesc.Usage = ResourceUsage.Default;
newDesc.CpuAccessFlags = CpuAccessFlags.None;
var textureAllocator = new TextureAllocator(mDevice, newDesc);
{
var timeStart = 0L;
var frameCount = 0;
var totalAFMilliseconds = 0.0;
var lastTimeStamp = 0L;
var frameAquired = false;
for (int i = 0; !stopCapture; i++)
{
var sleepAmount = Math.Max(0, (1000 / frameRate) / 2);
System.Threading.Thread.Sleep(sleepAmount);
frameCount++;
SharpDX.DXGI.Resource desktopResource = null;
var frameInfo_ = new OutputDuplicateFrameInformation();
var sw = new Stopwatch();
sw.Start();
var outputLoopCount = 0;
if (outputDuplication != null)
{//desktop duplication
int aquireFrameLoopCount = 0;
while (frameInfo_.LastPresentTime == 0)
{
aquireFrameLoopCount++;
if (aquireFrameLoopCount > 1)
{
System.Threading.Thread.Sleep(1);
}
if (frameAquired)
try
{
outputDuplication.ReleaseFrame();
frameAquired = false;
}
catch (Exception e)
{
}
try
{
outputDuplication.AcquireNextFrame(500, out frameInfo_, out desktopResource);
frameAquired = true;
}
catch (Exception e)
{
}
outputLoopCount++;
}
Debug.Assert(frameInfo_.LastPresentTime != 0);
Debug.Assert(frameInfo_.AccumulatedFrames != 0);
}
sw.Stop();
var frameCaptureTime = frameCount * TimeSpan.FromSeconds(1.0 / frameRate).Ticks;
frameCaptureTime = DateTime.Now.ToFileTimeUtc();
var aqTime = TimeSpan.FromSeconds((double)sw.ElapsedTicks / Stopwatch.Frequency);
totalAFMilliseconds += aqTime.TotalMilliseconds;
if (desktopResource != null)
using (var desktopTexture = desktopResource?.QueryInterface<Texture2D>())
{
Texture2D desktopTextureCopy = null;
try
{
desktopTextureCopy = textureAllocator.AllocateTexture();
}
catch (SharpDX.SharpDXException e)
{
var result = mDevice.DeviceRemovedReason;
result.CheckError();
throw e;
}
if (outputDuplication != null)
rgbToNv12.ConvertRGBToNV12(desktopTexture, desktopTextureCopy);
if (frameCount == 2)
timeStart = lastTimeStamp;
Console.WriteLine("Writing frame {0}", TimeSpan.FromTicks(frameCaptureTime - lastTimeStamp).ToString());
if (frameCount > 0)
{
var frameDuration = TimeSpan.FromSeconds(1.0 / frameRate).Ticks;
var time = (frameCount - 2) * frameDuration;
WriteFrame(sinkWriter, streamIndex, lastTimeStamp - timeStart, frameCaptureTime - lastTimeStamp, desktopTextureCopy.Description.Width, desktopTextureCopy.Description.Height, desktopTextureCopy, textureAllocator);
Console.WriteLine("frame written");
}
desktopTextureCopy.Dispose();
}
lastTimeStamp = frameCaptureTime;
CaptureTime = TimeSpan.FromTicks(lastTimeStamp - timeStart);
if (CaptureFramesChanged != null)
{
CaptureFramesChanged.Invoke(null, new EventArgs());
}
Console.WriteLine("end of loop");
}
stopCapture = false;
Console.WriteLine("Total AquireFrame time: {0}", totalAFMilliseconds / (300 * (1000 / frameRate)));
Console.WriteLine("Begining finalise");
sinkWriter.Finalize();
}
}
}
}
}
}
}
}
}
}
MediaManager.Shutdown();
}
谢谢
我修好了。似乎是一些多线程问题,并通过添加这些行得到修复:
using(var multiThread = mDevice.QueryInterface<Multithread>())
{
multiThread.SetMultithreadProtected(true);
}
在创建设备之后。
我正在尝试编写用于将 Windows 桌面录制到视频文件的应用程序。我正在使用桌面复制 API 和媒体基础 API。更具体地说,我使用的是 Media Foundation 的 SinkWriter
class。我正在使用桌面复制 API 中的 Texture2D
个对象并将它们传递给 SinkWriter
,这形成了一个很好的硬件加速管道。我也在使用 C# SharpDX
.
整个项目大部分都运行良好。它在我的 Surface Pro 上运行完美,捕捉相当一致的 fps 视频没有问题。
问题出在其他硬件上。具体来说,我已经测试过我已经在带有独立 Nvidia GPU 的 AMD Ryzen 上对此进行了测试。这也相当有效.. 除了通常在 8 到 20 秒之间记录桌面后,程序将被锁定在 outputDuplication.ReleaseFrame()
或 outputDuplication.AcquireNextFrame()
。它不会崩溃,它只是锁定了,所以我不确定如何弄清楚发生了什么。我确实知道它与将桌面纹理传递给 sinkWriter.WriteSample()
函数有关。如果我删除此行,并保持其他所有内容不变,就像往常一样继续创建 MediaBuffers
和 MediaSamples
,将它们与我的桌面纹理相关联,但不要将示例传递给WriteSample
方法,然后应用程序可以愉快地"record"看似无限期。
我检查了所有我能想到的潜在错误。我确保所有的对象都在它们应该被处理的时候被处理掉。我看不到内存泄漏。该代码在 MediaSample
上使用 SetAllocator()
方法,因此我传递给 SinkWriter
的纹理被回收。据我所知,这一切工作正常。
我怀疑这里有一些我不理解的基本知识。感觉我在正确管理 SharpDX
背后的非托管资源方面犯了一些错误,但我真的不知道从哪里开始看。此时将不胜感激任何指点。
这是主要的捕获代码。这不是全部,而是大部分。我怀疑问题出在这里。
static void WriteFrame(SinkWriter sinkWriter, int steamIndex, long time, long duration, int width, int height, Texture2D texture, TextureAllocator textureAllocator)
{
MediaBuffer textureBuffer;
MediaFactory.CreateDXGISurfaceBuffer(texture.GetType().GUID, texture, 0, false, out textureBuffer);
using (var buffer2d = textureBuffer.QueryInterface<Buffer2D>())
{
textureBuffer.CurrentLength = buffer2d.ContiguousLength;
}
using (var sample = MediaFactory.CreateVideoSampleFromSurface(null))
{
sample.SampleTime = time;
sample.SampleDuration = duration;
sample.AddBuffer(textureBuffer);
using(var trackedSample = sample.QueryInterface<TrackedSample>())
{
trackedSample.SetAllocator(textureAllocator, null);
}
sinkWriter.WriteSample(steamIndex, sample);
}
textureBuffer.Dispose();
}
void captureAction()
{
MediaManager.Startup(true);
int streamIndex = 0;
var whichOutputDevice = 0;
var whichAdapter = 0;
using (var factory = new Factory1())
{
using (var adapter = factory.GetAdapter1(whichAdapter))
{
using (var mDevice = new SharpDX.Direct3D11.Device(adapter))
{
using (var dXGIDeviceManager = new DXGIDeviceManager())
{
dXGIDeviceManager.ResetDevice(mDevice);
using (var output = adapter.GetOutput(whichOutputDevice))
{
using (var output1 = output.QueryInterface<Output1>())
{
var mOutputDesc = output.Description;
var mTextureDesc = new Texture2DDescription()
{
CpuAccessFlags = CpuAccessFlags.Read,
BindFlags = BindFlags.None,
Format = Format.B8G8R8A8_UNorm,
Width = mOutputDesc.DesktopBounds.Right - mOutputDesc.DesktopBounds.Left,
Height = mOutputDesc.DesktopBounds.Bottom - mOutputDesc.DesktopBounds.Top,
OptionFlags = ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = ResourceUsage.Staging
};
using (var outputDuplication = output1.DuplicateOutput(mDevice))
{
using (var sinkWriter = InitialiseSinkWriter(path, out streamIndex, mTextureDesc.Width, mTextureDesc.Height, frameRate, mDevice, dXGIDeviceManager, preferHardwareAcceleration))
{
using (var rgbToNv12 = new RGBToNV12ConverterD3D11(mDevice, mDevice.ImmediateContext, mTextureDesc.Width, mTextureDesc.Height))
{
var newDesc = mTextureDesc;
if (forceNV12)
newDesc.Format = Format.NV12;
newDesc.BindFlags = BindFlags.RenderTarget;
newDesc.Usage = ResourceUsage.Default;
newDesc.CpuAccessFlags = CpuAccessFlags.None;
var textureAllocator = new TextureAllocator(mDevice, newDesc);
{
var timeStart = 0L;
var frameCount = 0;
var totalAFMilliseconds = 0.0;
var lastTimeStamp = 0L;
var frameAquired = false;
for (int i = 0; !stopCapture; i++)
{
var sleepAmount = Math.Max(0, (1000 / frameRate) / 2);
System.Threading.Thread.Sleep(sleepAmount);
frameCount++;
SharpDX.DXGI.Resource desktopResource = null;
var frameInfo_ = new OutputDuplicateFrameInformation();
var sw = new Stopwatch();
sw.Start();
var outputLoopCount = 0;
if (outputDuplication != null)
{//desktop duplication
int aquireFrameLoopCount = 0;
while (frameInfo_.LastPresentTime == 0)
{
aquireFrameLoopCount++;
if (aquireFrameLoopCount > 1)
{
System.Threading.Thread.Sleep(1);
}
if (frameAquired)
try
{
outputDuplication.ReleaseFrame();
frameAquired = false;
}
catch (Exception e)
{
}
try
{
outputDuplication.AcquireNextFrame(500, out frameInfo_, out desktopResource);
frameAquired = true;
}
catch (Exception e)
{
}
outputLoopCount++;
}
Debug.Assert(frameInfo_.LastPresentTime != 0);
Debug.Assert(frameInfo_.AccumulatedFrames != 0);
}
sw.Stop();
var frameCaptureTime = frameCount * TimeSpan.FromSeconds(1.0 / frameRate).Ticks;
frameCaptureTime = DateTime.Now.ToFileTimeUtc();
var aqTime = TimeSpan.FromSeconds((double)sw.ElapsedTicks / Stopwatch.Frequency);
totalAFMilliseconds += aqTime.TotalMilliseconds;
if (desktopResource != null)
using (var desktopTexture = desktopResource?.QueryInterface<Texture2D>())
{
Texture2D desktopTextureCopy = null;
try
{
desktopTextureCopy = textureAllocator.AllocateTexture();
}
catch (SharpDX.SharpDXException e)
{
var result = mDevice.DeviceRemovedReason;
result.CheckError();
throw e;
}
if (outputDuplication != null)
rgbToNv12.ConvertRGBToNV12(desktopTexture, desktopTextureCopy);
if (frameCount == 2)
timeStart = lastTimeStamp;
Console.WriteLine("Writing frame {0}", TimeSpan.FromTicks(frameCaptureTime - lastTimeStamp).ToString());
if (frameCount > 0)
{
var frameDuration = TimeSpan.FromSeconds(1.0 / frameRate).Ticks;
var time = (frameCount - 2) * frameDuration;
WriteFrame(sinkWriter, streamIndex, lastTimeStamp - timeStart, frameCaptureTime - lastTimeStamp, desktopTextureCopy.Description.Width, desktopTextureCopy.Description.Height, desktopTextureCopy, textureAllocator);
Console.WriteLine("frame written");
}
desktopTextureCopy.Dispose();
}
lastTimeStamp = frameCaptureTime;
CaptureTime = TimeSpan.FromTicks(lastTimeStamp - timeStart);
if (CaptureFramesChanged != null)
{
CaptureFramesChanged.Invoke(null, new EventArgs());
}
Console.WriteLine("end of loop");
}
stopCapture = false;
Console.WriteLine("Total AquireFrame time: {0}", totalAFMilliseconds / (300 * (1000 / frameRate)));
Console.WriteLine("Begining finalise");
sinkWriter.Finalize();
}
}
}
}
}
}
}
}
}
}
MediaManager.Shutdown();
}
谢谢
我修好了。似乎是一些多线程问题,并通过添加这些行得到修复:
using(var multiThread = mDevice.QueryInterface<Multithread>())
{
multiThread.SetMultithreadProtected(true);
}
在创建设备之后。