2017-08-02 3 views
2

Ich versuche, eine Screenshot aller Bildschirme auf meinem PC zu erstellen. In der Vergangenheit habe ich die GDI-Methode verwendet, aber aufgrund von Leistungsproblemen versuche ich den DirectX-Weg.Erfassen aller Bildschirme mit DirectX GetFrontBufferData

ich einen Screenshot von einem einzigen Bildschirm ohne Probleme nehmen können, mit einem Code wie folgt aus:

using Microsoft.DirectX; 
using Microsoft.DirectX.Direct3D; 
using System.Windows.Forms; 
using System.Drawing;  

class Capture : Form 
{ 
    private Device device; 
    private Surface surface; 

    public Capture() 
    { 
     PresentParameters p = new PresentParameters(); 
     p.Windowed = true; 
     p.SwapEffect = SwapEffect.Discard; 
     device = new Device(0, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p); 
     surface = device.CreateOffscreenPlainSurface(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8B8G8R8, Pool.Scratch); 
    } 

    public Bitmap Frame() 
    { 
     GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface); 
     return new Bitmap(gs); 
    } 
} 

(Lets ignorieren für diese Frage die Bitmap aus dem Speicher zu löschen)

Kodex Mit ich kann Mache einen Screenshot von meinem Hauptbildschirm. Das Ändern des ersten Parameters des Device-Konstruktors auf eine andere Nummer entspricht einem anderen Bildschirm. Wenn ich 3 Bildschirme habe und 2 als Parameter übergebe, erhalte ich einen Screenshot von meinem dritten Bildschirm.

Das Problem, das ich habe, ist, wie mit der Erfassung aller Bildschirme umzugehen. Ich kam mit dem Follow-up:

class CaptureScreen : Form 
{ 
    private int index; 
    private Screen screen; 
    private Device device; 
    private Surface surface; 
    public Rectangle ScreenBounds { get { return screen.Bounds; } } 
    public Device Device { get { return device; } } 

    public CaptureScreen(int index, Screen screen, PresentParameters p) 
    { 
     this.screen = screen; this.index = index; 

     device = new Device(index, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p); 
     surface = device.CreateOffscreenPlainSurface(screen.Bounds.Width, screen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch); 
    } 

    public Bitmap Frame() 
    { 
     device.GetFrontBufferData(0, surface); 
     GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface); 
     return new Bitmap(gs); 
    } 
} 

class CaptureDirectX : Form 
{ 
    private CaptureScreen[] screens; 
    private int width = 0; 
    private int height = 0; 

    public CaptureDirectX() 
    { 
     PresentParameters p = new PresentParameters(); 
     p.Windowed = true; 
     p.SwapEffect = SwapEffect.Discard; 
     screens = new CaptureScreen[Screen.AllScreens.Length]; 
     for (int i = 0; i < Screen.AllScreens.Length; i++) 
     { 
      screens[i] = new CaptureScreen(i, Screen.AllScreens[i], p); 
      //reset previous devices 
      if (i > 0) 
      { 
       for(int j = 0; j < i; j++) 
       { 
        screens[j].Device.Reset(p); 
       } 
      } 
      width += Screen.AllScreens[i].Bounds.Width; 
      if (Screen.AllScreens[i].Bounds.Height > height) 
      { 
       height = Screen.AllScreens[i].Bounds.Height; 
      } 
     } 
    } 

    public Bitmap Frame() 
    { 
     Bitmap result = new Bitmap(width, height); 
     using (var g = Graphics.FromImage(result)) 
     { 
      for (int i = 0; i < screens.Length; i++) 
      { 
       Bitmap frame = screens[i].Frame(); 
       g.DrawImage(frame, screens[i].Bounds); 
      } 
     } 
     return result; 
    } 
} 

Wie Sie sehen können, ich aber die verfügbaren Bildschirme durchlaufen und mehrere Geräte und Oberflächen in einer separaten Klasse erstellen. Aber Aufruf Frame() der CaptureDirectX Klasse führt den folgenden Fehler:

An unhandled exception of type 'Microsoft.DirectX.Direct3D.InvalidCallException' occurred in Microsoft.DirectX.Direct3D.dll

Auf der Linie

device.GetFrontBufferData(0, surface); 

Ich habe diese ein wenig erforscht, aber hatte nicht viel Erfolg. Ich bin mir nicht sicher, was das Problem ist. Ich habe eine link gefunden, die eine Lösung bietet, die über das Zurücksetzen der Device Objekte spricht. Aber wie Sie in meinem Code oben sehen können, habe ich versucht, alle zuvor erstellten Device Objekte zurückzusetzen, leider ohne Erfolg.

Also meine Fragen sind:

  • Ist das, was ich sogar möglich, durch diese Methode (das heißt GetFrontBufferData) zu erreichen bin versucht?
  • Was mache ich falsch? Was vermisse ich?
  • Sehen Sie Leistungsprobleme bei der Aufnahme des Bildschirms mit hoher Geschwindigkeit, sagen wir 30 fps? (Einen einzigen Bildschirm mit einem Ziel von 30 Bildern pro Sekunde Capturing gab mir eine Geschwindigkeit von etwa 25 - 30 Bildern pro Sekunde, verglichen mit dem GDI methology der 15fps manchmal gerne sinkt)

FYI es ist eine WPF-Anwendung, dh .NET 4.5

Edit: Ich sollte erwähnen, dass ich IDXGI_DesktopDuplication bewusst bin, aber leider nicht meinen Anforderungen entspricht. Soweit ich weiß, ist diese API nur ab Windows 8 verfügbar, aber ich versuche, eine Lösung zu finden, die aufgrund meiner Clients ab Windows 7 funktioniert.

+0

https://StackOverflow.com/Questions/25681915/c-direct3d-multiple-screen-capture – VuVirt

+0

@VuVirt Ja, ich habe diese Frage gesehen, aber seine Lösung funktioniert nicht für mich. Ich kann mehrere Geräte problemlos erstellen, aber den Frontpuffer von ihnen zu bekommen, ist das Problem. – DodgerThud

+0

Ich denke, Sie sollten GetFrontBufferData für jeden Bildschirm (korrekte Korrektoren vorausgesetzt) ​​separat aufrufen. – VuVirt

Antwort

0

Nun, am Ende war die Lösung etwas völlig anderes. Die Klasse System.Windows.Forms.Screen spielt nicht gut mit den Klassen DirectX. Warum? Weil die Indizes nicht übereinstimmen. Das erste Objekt in AllScreens muss im Device nicht unbedingt den Index 0 haben.

Jetzt ist das normalerweise kein Problem, außer wenn Sie eine "seltsame" Monitorkonfiguration wie meine haben.Auf dem Schreibtisch habe ich 3 Bildschirme, eine vertikale (1200,1920), eine horizontale (1920, 1200) und eine andere horizontale Laptop-Bildschirm (1920, 1080).

Was in meinem Fall passiert ist: Das erste Objekt in AllScreens war der vertikale Monitor auf der linken Seite. Ich versuche ein Gerät für Index 0, 1200 Breite und 1920 Höhe zu erstellen. Index 0 entspricht jedoch meinem Hauptmonitor, d. H. Dem horizontalen Monitor in der Mitte. Also gehe ich mit meiner Installation aus dem Bildschirm. Die Initialisierung verursacht keine Ausnahme und irgendwann später versuche ich, die Daten des Frontpuffers zu lesen. Bam, Ausnahme, weil ich versuche, einen 1200x1920 Screenshot eines Monitors zu machen, der 1920x1200 ist.

Leider, auch nachdem ich das funktionierte, war die Leistung nicht gut. Ein einzelnes Bild von allen 3 Monitoren dauert etwa 300 bis 500 ms. Selbst mit einem einzigen Monitor war die Ausführungszeit ungefähr 100 ms. Nicht gut genug für meinen Anwendungsfall. Hat den Backbuffer auch nicht zur Arbeit gebracht, er produziert nur schwarze Bilder.

Ich ging zurück auf die GDI-Methode und verbesserte es, indem nur bestimmte Teile der Bitmap auf jedem Frame() Aufruf aktualisiert. Sie möchten eine Region von 1920x1200 erfassen, die in 480x300 Rechtecke geschnitten wird.

Verwandte Themen