2011-07-25 18 views
7

Implemento una función que tomará una captura de pantalla repetidamente y arrojará rectángulos sucios entre 2 disparos diferentes y luego volverá a dibujar la pantalla en una ventana.C# ¿Cuál es la manera más rápida de tomar una captura de pantalla?

Puedo conseguirlo entre 20 ~ 30FPS actualmente. Ya es aceptable. Pero luego hice un punto de referencia y medí su desempeño. Descubrió que el Graphics.CopyFromScreen() toma hasta el 50% del tiempo de procesamiento. (Sip. Incluso en el peor de los casos, aún lleva más tiempo que encontrar todos los rectángulos sucios) Luego usé la implementación API nativa BitBlt() y no obtuve ninguna mejora.

Sé que puede no haber ningún motivo práctico para hacerlo más rápido que 30FPS en este caso. Me pregunto, ¿hay alguna manera más rápida de tomar una captura de pantalla?

Gracias.

Respuesta

6

Esto es muy similar a una pregunta hecha hace varios años: Here. Esa pregunta era si las capacidades directx's capturing se podrían utilizar para obtener un mejor rendimiento.

El consenso fue que probablemente no proporcionaría ningún aumento en el rendimiento, TightVNC lo hace muy rápidamente haciendo trampa. Utiliza un controlador que no tiene que usar la API que (presumiblemente) .NET está usando.

En algún momento recuerdo haber visto el código fuente de Camstudio y creo que usan las capacidades de captura de directx. No creo que puedas empujar mucho más allá de 30 fps, y la mayoría de las veces ni siquiera eso. No estoy seguro si eso es un problema con los ganchos que camstudio usa para descubrir cuándo algo ha cambiado o el mecanismo de captura real.

+0

Gracias. No consideré esas instrucciones. Ahora tengo más opciones. – AKFish

0

Para la alternativa a CopyFromScreen() check this. Tenga en cuenta que el Graphics.CopyFromScreen() llama a la API BitBlt() para copiar de la pantalla, puede verificar el código fuente utilizando Reflector.

+2

u hoy en día incluso se puede mirar el código original aquí: http://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Graphics.cs,f76c9e39776eeb24 – Jens

+0

@jens sí de hecho, ya que Microsoft es ahora más inteligente al hacer esto disponible :) –

3

Para aquellos que vienen a este tema, he llegado a esta solución:

using SharpDX; 
using SharpDX.Direct3D11; 
using SharpDX.DXGI; 
using System; 
using System.Diagnostics; 
using System.Drawing; 
using System.Drawing.Imaging; 
using System.IO; 
using System.Threading.Tasks; 

Necesitará el paquete SharpDX y SharpDX.Direct3D11

public class ScreenStateLogger 
{ 
    private byte[] _previousScreen; 
    private bool _run, _init; 

    public int Size { get; private set; } 
    public ScreenStateLogger() 
    { 

    } 

    public void Start() 
    { 
     _run = true; 
     var factory = new Factory1(); 
     //Get first adapter 
     var adapter = factory.GetAdapter1(0); 
     //Get device from adapter 
     var device = new SharpDX.Direct3D11.Device(adapter); 
     //Get front buffer of the adapter 
     var output = adapter.GetOutput(0); 
     var output1 = output.QueryInterface<Output1>(); 

     // Width/Height of desktop to capture 
     int width = output.Description.DesktopBounds.Right; 
     int height = output.Description.DesktopBounds.Bottom; 

     // Create Staging texture CPU-accessible 
     var textureDesc = new Texture2DDescription 
     { 
      CpuAccessFlags = CpuAccessFlags.Read, 
      BindFlags = BindFlags.None, 
      Format = Format.B8G8R8A8_UNorm, 
      Width = width, 
      Height = height, 
      OptionFlags = ResourceOptionFlags.None, 
      MipLevels = 1, 
      ArraySize = 1, 
      SampleDescription = { Count = 1, Quality = 0 }, 
      Usage = ResourceUsage.Staging 
     }; 
     var screenTexture = new Texture2D(device, textureDesc); 

     Task.Factory.StartNew(() => 
     { 
      // Duplicate the output 
      using (var duplicatedOutput = output1.DuplicateOutput(device)) 
      { 
       while (_run) 
       { 
        try 
        { 
         SharpDX.DXGI.Resource screenResource; 
         OutputDuplicateFrameInformation duplicateFrameInformation; 

         // Try to get duplicated frame within given time is ms 
         duplicatedOutput.AcquireNextFrame(5, out duplicateFrameInformation, out screenResource); 

         // copy resource into memory that can be accessed by the CPU 
         using (var screenTexture2D = screenResource.QueryInterface<Texture2D>()) 
          device.ImmediateContext.CopyResource(screenTexture2D, screenTexture); 

         // Get the desktop capture texture 
         var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None); 

         // Create Drawing.Bitmap 
         using (var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb)) 
         { 
          var boundsRect = new 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 (int 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); 
          device.ImmediateContext.UnmapSubresource(screenTexture, 0); 

          using (var ms = new MemoryStream()) 
          { 
           bitmap.Save(ms, ImageFormat.Bmp); 
           ScreenRefreshed?.Invoke(this, ms.ToArray()); 
           _init = true; 
          } 
         } 
         screenResource.Dispose(); 
         duplicatedOutput.ReleaseFrame(); 
        } 
        catch (SharpDXException e) 
        { 
         if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code) 
         { 
          Trace.TraceError(e.Message); 
          Trace.TraceError(e.StackTrace); 
         } 
        } 
       } 
      } 
     }); 
     while (!_init) ; 
    } 

    public void Stop() 
    { 
     _run = false; 
    } 

    public EventHandler<byte[]> ScreenRefreshed; 
} 

Este código será llegar lo más rápido posible los fotogramas del buffer frontal del dispositivo gráfico y recuperar el byte [] del mapa de bits que crea. El código parece ser estable en la memoria y el uso del procesador (GPU y CPU).

uso:

var screenStateLogger = new ScreenStateLogger(); 
screenStateLogger.ScreenRefreshed += (sender, data) => 
{ 
    //New frame in data 
}; 
screenStateLogger.Start(); 
+0

Obtuve 17-18 fps a 3840x2160 y ~ 60 fps a 1920x1080 con Fury X e i7-3770. –

Cuestiones relacionadas