Неверный вызов функции ReleaseFrame () в API DesktopDuplication - PullRequest
6 голосов
/ 13 февраля 2020

У пользователя моего приложения возникли проблемы с захватом экрана с помощью DesktopDuplication API.

. При запуске захвата приложение вылетает, потому что приложение не может освободить кадр OutputDuplication.

Сведения о пользователе C:

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, что мне делать?
Кроме того, как я могу отладить такого рода ошибки на пользователях P C (не на машине разработчика) ?


РЕДАКТИРОВАТЬ:

Вот что я пытаюсь сделать:

С включенным 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

Затем я настроил приложение для запуска на выделенном графическом процессоре на моем ноутбуке, так как Я точно знаю, что это вызовет 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 / Unsupported ], Сообщение: указанный интерфейс устройства или уровень функции не поддерживаются в этой системе.

Затем я попытался снова, но на этот раз только с DeviceCreationFlags.Debug, и это сработало. Я все еще анализирую файл.

Debug

1 Ответ

0 голосов
/ 04 марта 2020

Кажется, что мы разделили тот же код для C# дублирования рабочего стола, у меня также были некоторые проблемы с выпуском кадров. Я немного изменил его, и на данный момент у меня нет проблем с моим (пока не полным) приложением ambilight. Вы можете посмотреть код здесь: https://github.com/leocb/Ambilight не стесняйтесь копировать любую часть кода, который вам нравится. Соответствующая часть для вас находится внутри проекта DesktopDuplication, также проверьте функции, которые я использую на ConfigForm.cs

. Я игнорирую любые ошибки, которые ReleaseFrame() может выдать, поместив его в пустую строку try / catch. IMO, единственное важное, что вы должны проверить, это DXGI_ERROR_ACCESS_LOST, потому что в этом случае вам понадобится новый экземпляр IDXGIOutputDuplication (я пока не делаю этого в своем коде). Я не мог измерить потери производительности, выполнив это.

Убедитесь, что вы правильно инициализируете SharpDX (устройство, фабрика и т. Д. c ..) и устанавливаете правильные флаги для текстуры поверхности

Я рекомендую использовать двойной буфер для образа GDI, это позволяет избежать ошибок состояния гонки и повышает производительность

Кроме того, по соображениям производительности я выполняю код захвата в другом потоке и вызываю только ReleaseFrame() непосредственно перед выполнением следующего захвата, как , рекомендованный Microsoft :

[...], мы рекомендуем освободить кадр непосредственно перед вызовом метода IDXGIOutputDuplication :: AcquireNextFrame для получения следующий кадр. Когда клиент не владеет фреймом, операционная система копирует все обновления рабочего стола на поверхность.

Если все это не удается, «более простыми» решениями будет захват позиции мыши со стандартным c# код и нарисуйте его сверху экрана захвата

...