【问题标题】:Deadlock on AcquireNextFrame() and ReleaseFrame() randomly in Desktop Duplication API桌面复制 API 中的 AcquireNextFrame() 和 ReleaseFrame() 随机死锁
【发布时间】:2018-12-11 21:44:43
【问题描述】:

我正在尝试编写用于将 Windows 桌面录制到视频文件的应用程序。我正在使用桌面复制 API 和媒体基础 API。更具体地说,我使用的是 Media Foundation 的 SinkWriter 类。我正在使用来自桌面复制 API 的 Texture2D 对象并将它们传递给 SinkWriter,这使得硬件加速管道非常好。我也在使用 C# 和 SharpDX。

整个项目大部分都运行良好。它在我的 Surface Pro 上完美运行,毫无问题地捕捉相当一致的 fps 视频。

问题出在其他硬件上。具体来说,我已经在带有独立 Nvidia GPU 的 AMD Ryzen 上进行了测试。这也很好用.. 除了通常在 8 到 20 秒之间录制桌面后,程序将被锁定在 outputDuplication.ReleaseFrame() 或 outputDuplication.AcquireNextFrame() 上。它不会崩溃,它只是锁定,所以我不确定如何弄清楚发生了什么。我确实知道它与将桌面纹理传递给sinkWriter.WriteSample() 函数有关。如果我删除此行,并保持所有其他内容相同,如继续照常创建MediaBuffers 和MediaSamples,将它们与我的桌面纹理相关联,但不要将示例传递给WriteSample 方法,那么应用程序就可以愉快地看似无限期地“记录”。

我已经检查了我能想到的每一个潜在错误。我确保所有对象都在应该处理的时候被处理。我看不到内存泄漏。该代码在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();
}

谢谢

【问题讨论】:

  • 如何将 DirectX11 设备处理程序设置为视频编码器?在 Surface Pro 中,有一个带有硬件加速 H264 编码器的 GPU - 因此,DirectX11 设备处理程序没有问题 - GPU 和 HA H264 共享相同的视频内存。在您的测试系统AMD Ryzen with a discrete Nvidia GPU 中,您可能会遇到以下情况:NVIDIA 中的视频内存,但 Ryzen 中的 HA H264!?!
  • @EvgenyPereguda dXGIDeviceManager 设置为使用与用于呈现桌面的设备相同的设备。该设备被传递给SinkWriter。 SinkWriter 自动选择要使用的最佳硬件编码器,在本例中是 NVidia (NVEnc) 编码器。
  • NVidia GPU 的问题在于并非所有的 GPU 都有 NVEnc - 一些型号已经锁定了 NVEnc - GeForce 840m 4gb NVENC + SHADOWPLAY
  • @EvgenyPereguda 不是我正在使用的 GPU,因为它可以完美记录 8-20 秒左右。如果我在它冻结之前结束录制,它会生成一个漂亮的 h.264 mp4 视频并保存到我的文件系统中。唯一的问题是,如果我按照描述记录足够长的时间,它最终会随机锁定。
  • BeginWriting 怎么样?我在你的代码中找不到这个方法,但是You must call BeginWriting before calling IMFSinkWriter::WriteSample!?!

标签: directx video-capture ms-media-foundation sharpdx desktop-duplication


【解决方案1】:

我修好了。似乎这是一些多线程问题,并通过添加这些行来修复:

using(var multiThread = mDevice.QueryInterface<Multithread>())
{
    multiThread.SetMultithreadProtected(true);
}

在创建设备之后。

【讨论】:

    猜你喜欢
    • 2018-09-04
    • 2017-11-08
    • 2017-03-07
    • 2016-09-17
    • 1970-01-01
    • 1970-01-01
    • 2017-09-21
    • 1970-01-01
    • 2018-07-11
    相关资源
    最近更新 更多