2017-08-02 187 views
2

我正試圖在PC上創建所有屏幕的屏幕截圖。在過去,我一直在使用GDI方法,但由於性能問題,我正在嘗試DirectX方式。使用DirectX捕獲所有屏幕GetFrontBufferData

我可以採取截圖的單一屏幕沒有問題,用這樣的代碼:

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); 
    } 
} 

(讓我們忽略刪除從內存中的位圖這個問題)

與該代碼我可以採取我的主要屏幕的屏幕截圖。將Device構造函數的第一個參數更改爲不同的數字對應於不同的屏幕。如果我有3個屏幕,並且我通過2作爲參數,我會看到我的第三個屏幕的屏幕截圖。

我遇到的問題是如何處理捕獲所有屏幕。我想出了以下幾點:

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; 
    } 
} 

正如你所看到的,我遍歷可用的屏幕並在一個單獨的類中創建多個設備和曲面。但在調用CaptureDirectX類的Frame()引發以下錯誤:

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

在生產線

device.GetFrontBufferData(0, surface); 

我一直在研究這個有點,但沒有一大堆的成功。我不確定問題是什麼。 我找到了一個link,它提供了一個解決方案,用於重置Device對象。但正如您在上面的代碼中看到的,我一直在嘗試重置以前創建的所有對象,遺憾的是沒有成功。

所以我的問題是:

  • 是什麼,我想通過這個方法(即GetFrontBufferData)實現甚至可能嗎?
  • 我在做什麼錯?我錯過了什麼?
  • 當您以高速率捕捉屏幕時,您是否看到任何性能問題,比如說30 fps? (拍攝有30fps的的目標單一的屏幕給了我25的速度 - 每秒30幀,與GDI計量學下沉有時喜歡15fps的比較)

FYI這是一個WPF應用程序,即.NET 4.5

編輯:我應該提到,我知道IDXGI_DesktopDuplication,但可悲的是,它不符合我的要求。據我所知,該API僅在Windows 8以上版本中可用,但我試圖從我的客戶端獲得Windows 7以上的解決方案。

+0

https://stackoverflow.com/questions/25681915/c-direct3d-multiple-screen-capture – VuVirt

+0

@VuVirt是的,我已經看到了這個問題,但他的解決方案並不適合我。我可以創建多個設備,但從它們獲取前端緩衝區是問題所在。 – DodgerThud

+0

我認爲你應該爲每個屏幕(提供正確的座標)分別調用GetFrontBufferData。 – VuVirt

回答

0

那麼,最終解決方案是完全不同的。 System.Windows.Forms.Screen班級與DirectX班級不搭配很好。爲什麼?因爲索引不匹配。 AllScreens中的第一個對象並不一定必須在Device instatiation中指數爲0。

現在通常這不是一個問題,除非你有像我這樣的「奇怪的」監視器設置。在桌子上,我有3個屏幕,一個垂直(1200,1920),一個水平(1920,1200)和另一個水平筆記本電腦屏幕(1920,1080)。

我的情況發生了什麼:AllScreens中的第一個對象是左側的垂直監視器。我嘗試創建索引0,1200寬度和1920高度的設備。索引0對應於我的主監視器,即中間的水平監視器。所以我基本上就是用我的instatiation走出屏幕界限。 instatiation不會拋出異常,稍後我會嘗試讀取前端緩衝區數據。 Bam,Exception,因爲我試圖截取1920x1200的顯示器的1200x1920屏幕截圖。

不幸的是,即使我得到這個工作後,表現並不好。所有3臺顯示器的一個框架大約需要300到500毫秒。即使只有一臺顯示器,執行時間也是100ms。對我的用例不夠好。 沒有得到Backbuffer工作,它只是產生黑色的圖像。

我回到GDI方法,並通過只更新每個Frame()調用的位圖的特定塊來增強它。您想要捕獲一個1920x1200區域,該區域將被切割成480x300的矩形。