Metodo più veloce di acquisizione dello schermo su Windows


159

Voglio scrivere un programma di screencasting per la piattaforma Windows, ma non sono sicuro di come catturare lo schermo. L'unico metodo di cui sono a conoscenza è utilizzare GDI, ma sono curioso di sapere se ci sono altri modi per farlo e, se ce ne sono, che comporta il minimo overhead? La velocità è una priorità.

Il programma di screencasting sarà per la registrazione di filmati di gioco, anche se, se questo restringe le opzioni, sono ancora aperto per qualsiasi altro suggerimento che esca da questo ambito. La conoscenza non è male, dopo tutto.

Modifica : mi sono imbattuto in questo articolo: vari metodi per catturare lo schermo . Mi ha fatto conoscere il modo di farlo dell'API di Windows Media e il modo di farlo con DirectX. Cita la conclusione che disabilitare l'accelerazione hardware potrebbe migliorare drasticamente le prestazioni dell'applicazione di acquisizione. Sono curioso di sapere perché. Qualcuno potrebbe riempire gli spazi vuoti mancanti per me?

Modifica : ho letto che i programmi di screencasting come Camtasia usano il proprio driver di acquisizione. Qualcuno potrebbe darmi una spiegazione approfondita su come funziona e perché è più veloce? Potrei anche aver bisogno di una guida per l'implementazione di qualcosa del genere, ma sono sicuro che ci sia comunque documentazione esistente.

Inoltre, ora so come FRAPS registra lo schermo. Aggancia l'API grafica sottostante per leggere dal back buffer. Da quello che ho capito, questo è più veloce della lettura dal buffer anteriore, perché stai leggendo dalla RAM di sistema, piuttosto che dalla RAM video. Puoi leggere l'articolo qui .


Hai considerato, anziché registrare graficamente i contenuti dello schermo, un sistema di riproduzione ?
Benjamin Lindley,

2
Non devi agganciare nulla. Devi solo scrivere i tuoi eventi di input in modo che non controllino direttamente il gioco, ma chiamino invece altre funzioni. Ad esempio, se il giocatore preme il tasto sinistro, non si riduce semplicemente la posizione x dei giocatori. Invece, si chiama una funzione, come MovePlayerLeft(). Inoltre, registri l'ora e la durata della pressione dei tasti e di altri input. Quindi, quando sei in modalità riproduzione, semplicemente ignori l'ingresso e leggi invece i dati registrati. Se, nei dati, vedi premere un tasto sinistro, chiami MovePlayerLeft().
Benjamin Lindley,

1
@PigBen Questa sarà un'applicazione generica per la registrazione di filmati di gioco. Non è per un gioco specifico. Qualcuno che preme il tasto sinistro potrebbe significare spostare a destra, per quanto ne so. Inoltre, non hai preso in considerazione eventi che non sono influenzati dall'utente. 'E il rendering?
someguy

1
@someguy Ok immagino tu stia facendo qualcosa di molto più intenso, avevo aggiunto una routine usando i metodi sopra per salvare gli AVI replay in un gioco a circa 30 fps senza intoppi. Ho realizzato uno screen recorder con monitor multipli usando l'API di Windows per "l'ottimizzazione della forza lavoro", ma che ha funzionato male anche a 4fps mirati.
AJG85,

1
Esiste un driver mirror open source per Windows sul sito del repository di UltraVNC qui ultravnc.svn.sourceforge.net/viewvc/ultravnc/…
Beached

Risposte:


62

Questo è quello che uso per raccogliere singoli frame, ma se lo modifichi e tieni sempre aperti i due target, puoi "trasmetterlo" su disco usando un contatore statico per il nome del file. - Non ricordo dove l'ho trovato, ma è stato modificato, grazie a chiunque!

void dump_buffer()
{
   IDirect3DSurface9* pRenderTarget=NULL;
   IDirect3DSurface9* pDestTarget=NULL;
     const char file[] = "Pickture.bmp";
   // sanity checks.
   if (Device == NULL)
      return;

   // get the render target surface.
   HRESULT hr = Device->GetRenderTarget(0, &pRenderTarget);
   // get the current adapter display mode.
   //hr = pDirect3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddisplaymode);

   // create a destination surface.
   hr = Device->CreateOffscreenPlainSurface(DisplayMde.Width,
                         DisplayMde.Height,
                         DisplayMde.Format,
                         D3DPOOL_SYSTEMMEM,
                         &pDestTarget,
                         NULL);
   //copy the render target to the destination surface.
   hr = Device->GetRenderTargetData(pRenderTarget, pDestTarget);
   //save its contents to a bitmap file.
   hr = D3DXSaveSurfaceToFile(file,
                              D3DXIFF_BMP,
                              pDestTarget,
                              NULL,
                              NULL);

   // clean up.
   pRenderTarget->Release();
   pDestTarget->Release();
}

Grazie. Ho sentito parlare di questo metodo qualche tempo fa che si diceva fosse più veloce della lettura dal buffer frontale. Lo fai onestamente in quel modo e funziona correttamente?
someguy

Il problema con il buffer frontale è quello dell'accesso, cioè del tentativo di copiare un piano che al momento viene reso "interrompe" la copia. Funziona abbastanza bene per me e mangia il mio disco rigido!
Brandrew,

@bobobobo Non so come funzionerebbe esattamente, ma stavo pensando di usare qualcosa come Huffyuv. Modifica: O forse consentire all'utente di scegliere tra i filtri disponibili per directshow.
someguy

1
Non riesco proprio a farlo funzionare ... DirectX sputa alcune chiamate non valide sulla parte GetRenderTargetData. Ovviamente il modo in cui crei il tuo dispositivo deve avere molta importanza.
LightStriker l'

12
downvote, funziona solo per la propria applicazione, quindi non può essere utilizzato per registrare programmi generici
user3125280,

32

EDIT: posso vedere che questo è elencato sotto il tuo primo link di modifica come "il modo GDI". Questo è ancora un modo decente di andare anche con la consulenza sulle prestazioni su quel sito, puoi arrivare facilmente a 30 fps, penso.

Da questo commento (non ho esperienza nel fare questo, sto solo facendo riferimento a qualcuno che lo fa):

HDC hdc = GetDC(NULL); // get the desktop device context
HDC hDest = CreateCompatibleDC(hdc); // create a device context to use yourself

// get the height and width of the screen
int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);

// create a bitmap
HBITMAP hbDesktop = CreateCompatibleBitmap( hdc, width, height);

// use the previously created device context with the bitmap
SelectObject(hDest, hbDesktop);

// copy from the desktop device context to the bitmap device context
// call this once per 'frame'
BitBlt(hDest, 0,0, width, height, hdc, 0, 0, SRCCOPY);

// after the recording is done, release the desktop context you got..
ReleaseDC(NULL, hdc);

// ..delete the bitmap you were using to capture frames..
DeleteObject(hbDesktop);

// ..and delete the context you created
DeleteDC(hDest);

Non sto dicendo che questo è il più veloce, ma l' BitBltoperazione è generalmente molto veloce se stai copiando tra contesti di dispositivi compatibili.

Per riferimento, Open Broadcaster Software implementa qualcosa del genere come parte del loro metodo "dc_capture" , sebbene invece di creare il contesto di destinazione hDestusando CreateCompatibleDCloro usano un IDXGISurface1, che funziona con DirectX 10+. Se non c'è supporto per questo, ricadono CreateCompatibleDC.

Per cambiarlo per utilizzare un'applicazione specifica, è necessario modificare la prima riga in GetDC(game)cui si gametrova la maniglia della finestra del gioco, quindi impostare la destra heighte anche widthla finestra del gioco.

Una volta che hai i pixel in hDest / hbDesktop, devi ancora salvarli in un file, ma se stai catturando lo schermo, penso che vorresti bufferizzare un certo numero di loro in memoria e salvare nel file video in blocchi, quindi non indicherò il codice per il salvataggio di un'immagine statica su disco.


3
msdn.microsoft.com/en-us/library/dd183370%28VS.85%29.aspx Estratto: se i formati di colore dei contesti del dispositivo di origine e di destinazione non corrispondono, la funzione BitBlt converte il formato del colore di origine in modo che corrisponda alla destinazione formato.

2
Alcune prove a sostegno di ciò sarebbero buone. Hai pubblicato un confronto delle prestazioni da qualche parte o ne hai visto uno accurato?

2
Prova a profilarlo. Come ho detto nel post, mi riferisco a qualcuno che ha esperienza con GDI. Se perde memoria e sai come risolverlo, modifica il post per eliminare la perdita.

1
Ho ottenuto circa 5 fps con questo metodo. Questo era su un laptop con grafica integrata, quindi mi sarei aspettato di meglio da un desktop con una vera scheda grafica, ma è comunque molto lento.
Timmmm,

1
@Timmmm Ho aggiunto un riferimento al modo in cui OBS lo implementa. Spero che questo possa accelerare un po 'le cose per te.

19

Ho scritto un software di acquisizione video, simile a FRAPS per le applicazioni DirectX. Il codice sorgente è disponibile e il mio articolo spiega la tecnica generale. Guarda http://blog.nektra.com/main/2013/07/23/instrumenting-direct3d-applications-to-capture-video-and-calculate-frames-per-second/

Rispetto alle tue domande relative alle prestazioni,

  • DirectX dovrebbe essere più veloce di GDI tranne quando stai leggendo dal frontbuffer che è molto lento. Il mio approccio è simile a FRAPS (lettura da backbuffer). Intercetto una serie di metodi dalle interfacce Direct3D.

  • Per la registrazione video in tempo reale (con un impatto minimo sull'applicazione), è essenziale un codec veloce. FRAPS utilizza il proprio codec video lossless. Lagarith e HUFFYUV sono codec video senza perdita generici progettati per applicazioni in tempo reale. Dovresti guardarli se vuoi produrre file video.

  • Un altro approccio alla registrazione di screencast potrebbe essere quello di scrivere un Mirror Driver. Secondo Wikipedia: Quando il mirroring video è attivo, ogni volta che il sistema attinge al dispositivo video principale in una posizione all'interno dell'area speculare, una copia dell'operazione di disegno viene eseguita sul dispositivo video speculare in tempo reale. Vedi i driver mirror su MSDN: http://msdn.microsoft.com/en-us/library/windows/hardware/ff568315(v=vs.85).aspx .


16

Uso d3d9 per ottenere il backbuffer e lo salvo in un file png usando la libreria d3dx:

    IDirect3DSurface9 * surface;

    // GetBackBuffer
    idirect3ddevice9-> GetBackBuffer (0, 0, D3DBACKBUFFER_TYPE_MONO e superficie);

    // salva la superficie
    D3DXSaveSurfaceToFileA ("nome file.png", D3DXIFF_PNG, superficie, NULL, NULL);

    SAFE_RELEASE (superficie);

Per fare questo dovresti creare il tuo swapbuffer con

d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(Quindi garantisci che il backbuffer non è alterato prima di fare lo screenshot).


Ha senso, grazie. Sai qual è la differenza tra questo ed GetRenderTargetè?
Someguy,

Questo ottiene solo il target di rendering corrente (potrebbe essere un'altra superficie fuori schermo se qualcuno sta eseguendo il rendering alla trama nel momento in cui chiami).
Bobobobo,

13

Nella mia impressione, l'approccio GDI e l'approccio DX sono diversi nella sua natura. la pittura con GDI applica il metodo FLUSH, l'approccio FLUSH disegna il fotogramma, quindi lo cancella e ridisegna un altro fotogramma nello stesso buffer, ciò comporterà uno sfarfallio nei giochi che richiedono un frame rate elevato.

  1. PERCHÉ DX più veloce? in DX (o mondo grafico), viene applicato un metodo più maturo chiamato rendering a doppio buffer, in cui sono presenti due buffer, quando è presente il buffer anteriore all'hardware, è possibile eseguire il rendering anche sull'altro buffer, quindi dopo che il frame 1 è al termine del rendering, il sistema passa all'altro buffer (bloccandolo per la presentazione all'hardware e rilascia il buffer precedente), in questo modo l'inefficienza del rendering è notevolmente migliorata.
  2. PERCHÉ abbassare più rapidamente l'accelerazione hardware? sebbene con il doppio rendering del buffer, l'FPS sia migliorato, ma il tempo per il rendering è ancora limitato. l'hardware grafico moderno di solito comporta molta ottimizzazione durante il rendering, in genere come l'antialiasing, questo è molto intenso dal punto di vista del calcolo, se non hai bisogno di quella grafica di alta qualità, ovviamente puoi semplicemente disabilitare questa opzione. e questo ti farà risparmiare un po 'di tempo.

Penso che ciò di cui hai veramente bisogno sia un sistema di riproduzione, che sono totalmente d'accordo con ciò di cui la gente ha discusso.


1
Vedi la discussione sul perché un sistema di riproduzione non è fattibile. Il programma di screencasting non è per alcun gioco specifico.
someguy,

10

Ho scritto una classe che ha implementato il metodo GDI per l'acquisizione dello schermo. Anch'io volevo maggiore velocità, quindi, dopo aver scoperto il metodo DirectX (tramite GetFrontBuffer) l'ho provato, aspettandomi che fosse più veloce.

Sono rimasto sgomento per scoprire che GDI si comporta circa 2,5 volte più velocemente. Dopo 100 prove di acquisizione del mio display a doppio monitor, l'implementazione di GDI è stata in media di 0,65 per acquisizione dello schermo, mentre il metodo DirectX è stato in media di 1,72 secondi. Quindi GDI è decisamente più veloce di GetFrontBuffer, secondo i miei test.

Non sono riuscito a far funzionare il codice di Brandrew per testare DirectX tramite GetRenderTargetData. La copia dello schermo uscì puramente nera. Tuttavia, potrebbe copiare lo schermo vuoto in modo super veloce! Continuerò a armeggiare con quello e spero di ottenere una versione funzionante per vedere risultati reali da esso.


Grazie per l'informazione. Non ho testato il codice di Brandrew, ma so che adottare l' GetRenderTargetDataapproccio funziona. Forse scriverò la mia risposta al termine della domanda. Oppure, potresti aggiornare il tuo dopo aver fatto funzionare tutto.
someguy

0.65 per Screencapture ?! Una buona implementazione GDI (mantenendo i dispositivi in ​​giro, ecc.) Dovrebbe fare facilmente 30fps in 1920x1200 su un computer moderno.
Christopher Oezbek,

Suppongo che la qualità dell'immagine resa da GDI sia decisamente più scadente del DX
tintinnare il

Ho eseguito questo test in C # con SlimDX e, sorprendentemente, ho trovato gli stessi risultati. Forse questo potrebbe avere a che fare con il fatto che, usando SlimDX, si deve creare un nuovo stream e una nuova bitmap per ogni aggiornamento del frame, invece di crearlo una volta, riavvolgere e continuare a sovrascrivere la stessa posizione.
Cesar,

Solo un chiarimento: in realtà, gli "stessi risultati" si riferivano a GDI più veloce - come menzionato da @Christopher, 30fps + era molto fattibile e lasciava ancora molta CPU di riserva.
Cesar,

8

Alcune cose che sono stato in grado di spigolare: apparentemente usare un "mirror driver" è veloce anche se non ne sono consapevole.

Perché RDP è così veloce rispetto ad altri software di controllo remoto?

Anche apparentemente usando alcune convoluzioni di StretchRect sono più veloci di BitBlt

http://betterlogic.com/roger/2010/07/fast-screen-capture/comment-page-1/#comment-5193

E quello che hai citato (trappole agganciate alla D3D dll) è probabilmente l'unico modo per le applicazioni D3D, ma non funzionerà con l'acquisizione desktop di Windows XP. Quindi ora vorrei solo che ci fosse un frappole equivalente in termini di velocità per le normali finestre desktop ... qualcuno?

(Penso che con aero potresti essere in grado di usare ganci simili a frappole, ma gli utenti di XP sarebbero sfortunati).

Apparentemente anche modificando la profondità dei bit dello schermo e / o disabilitando l'accel hardware. potrebbe aiutare (e / o disabilitare l'aereo).

https://github.com/rdp/screen-capture-recorder-program include un'utilità di acquisizione basata su BitBlt ragionevolmente veloce e un benchmarker come parte della sua installazione, che può consentire di eseguire il benchmark delle velocità BitBlt per ottimizzarle.

VirtualDub ha anche un modulo di cattura dello schermo "opengl" che si dice sia veloce e fa cose come il rilevamento delle modifiche http://www.virtualdub.org/blog/pivot/entry.php?id=290


Mi chiedo, sarà più veloce usare il tuo "screen-capture-recorder" o usare BitBlt da solo? C'è qualche ottimizzazione nel tuo progetto?
blez,

Se usi Bitblt da solo potresti evitare un ulteriore memcpy (in genere memcpy non è il più grande collo di bottiglia, anche se aggiunge un po 'di tempo - lo stavo solo menzionando qui per la sua utility di benchmark, ma se hai bisogno di qualcosa o directhow è bello )
rogerdpack,

8

Per C ++ è possibile utilizzare: http://www.pinvoke.net/default.aspx/gdi32/BitBlt.html
Questo potrebbe non funzionare su tutti i tipi di applicazioni 3D / app video. Quindi questo link potrebbe essere più utile in quanto descrive 3 diversi metodi che è possibile utilizzare.

Vecchia risposta (C #):
è possibile utilizzare System.Drawing.Graphics.Copy , ma non è molto veloce.

Un progetto di esempio che ho scritto facendo esattamente questo: http://blog.tedd.no/index.php/2010/08/16/c-image-analysis-auto-gaming-with-source/

Sto programmando di aggiornare questo esempio utilizzando un metodo più veloce come Direct3D: http://spazzarama.com/2009/02/07/screencapture-with-direct3d/

Ed ecco un link per l'acquisizione di video: come catturare lo schermo per essere video usando C # .Net?


2
Ah, ho dimenticato di menzionare che sto programmando in C (possibilmente C ++) e non ho intenzione di usare .NET. Terribilmente dispiaciuto :/.
someguy

Conoscevo già BitBlt (GDI). Esaminerò Direct3D, comunque. Grazie!
someguy

Lo stavo esaminando qualche settimana fa, ma non sono ancora riuscito a implementarlo. Direct3D è !! !! più veloce del metodo incorporato C # che utilizza GDI +.
Tedd Hansen,

Ho aggiornato il mio post originale. Secondo il collegamento, DirectX è lento quando si deve chiamare GetFrontBufferData (). È qualcosa da considerare quando si registrano filmati di gioco? Potresti contestualizzare questo per me?
someguy

1
GDI è lento, quindi non è adatto al dominio del problema, DirectX o OpenGL sarebbe l'unica raccomandazione ragionevole.

6

Con Windows 8, Microsoft ha introdotto l'API di duplicazione desktop di Windows. Questo è il modo ufficialmente raccomandato di farlo. Una caratteristica interessante che ha per lo screencasting è che rileva il movimento della finestra, quindi puoi trasmettere i delta di blocco quando le finestre vengono spostate, anziché i pixel non elaborati. Inoltre, ti dice quali rettangoli sono cambiati, da una cornice all'altra.

Il codice di esempio Microsoft è piuttosto complesso, ma l'API è in realtà semplice e facile da usare. Ho messo insieme un progetto di esempio che è molto più semplice dell'esempio ufficiale:

https://github.com/bmharper/WindowsDesktopDuplicationSample

Documenti: https://docs.microsoft.com/en-gb/windows/desktop/direct3ddxgi/desktop-dup-api

Codice ufficiale di esempio Microsoft: https://code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a


Il progetto Github funziona perfettamente su Windows 10, testato con video web e Resident Evil 7. La cosa migliore è la scala di carico della CPU con la frequenza di aggiornamento della grafica.
jw_

non appena avvii GPU-Z questo programma smette di afferrare lo schermo.
Marino Šimić,

5

Puoi provare il progetto open source c ++ WinRobot @git , un potente catturatore di schermate

CComPtr<IWinRobotService> pService;
hr = pService.CoCreateInstance(__uuidof(ServiceHost) );

//get active console session
CComPtr<IUnknown> pUnk;
hr = pService->GetActiveConsoleSession(&pUnk);
CComQIPtr<IWinRobotSession> pSession = pUnk;

// capture screen
pUnk = 0;
hr = pSession->CreateScreenCapture(0,0,1280,800,&pUnk);

// get screen image data(with file mapping)
CComQIPtr<IScreenBufferStream> pBuffer = pUnk;

Supporto :

  • Finestra UAC
  • Winlogon
  • DirectShowOverlay

1
Questo è ... un sacco di ganci di basso livello. La velocità di acquisizione è sorprendente se hai i diritti di amministratore.
toster-cx,

Ho trovato il winrobot molto potente e fluido.
ashishgupta_mca,

Ho studiato il codice WinRobot e non ho visto nulla di innovativo nella cattura dello schermo wrt: utilizza lo stesso CreateCompatibleDC..BitBlt. A meno che non ci sia un po 'di magia quando questo viene eseguito nel contesto di servizio?
Shekh,

@shekh: il tuo studio era troppo superficiale. Il codice utilizza IDirectDrawSurface7-> BltFast () per copiare lo schermo dalla superficie del disegno dello schermo a una superficie DD copia, quindi utilizza un Filemapping per copiare l'immagine. È piuttosto complesso perché il codice è in esecuzione in un servizio in cui non è possibile accedere facilmente ai desktop.
Elmue

2

io stesso lo faccio con directx e penso che sia veloce come vorresti che fosse. non ho un esempio di codice rapido, ma ho trovato questo che dovrebbe essere utile. la versione di directx11 non dovrebbe differire molto, directx9 forse un po 'di più, ma questa è la strada da percorrere


2

Mi rendo conto che il seguente suggerimento non risponde alla tua domanda, ma il metodo più semplice che ho trovato per catturare una vista DirectX in rapida evoluzione, è quello di collegare una videocamera alla porta S-video della scheda video e registrare le immagini come un film. Quindi trasferire il video dalla videocamera in un file MPG, WMV, AVI ecc. Sul computer.


2

La registrazione dello schermo può essere eseguita in C # usando l' API VLC . Ho fatto un programma di esempio per dimostrarlo. Utilizza le librerie LibVLCSharp e VideoLAN.LibVLC.Windows . È possibile ottenere molte più funzionalità relative al rendering video utilizzando questa API multipiattaforma.

Per la documentazione dell'API, consultare: LibVLCSharp API Github

using System;
using System.IO;
using System.Reflection;
using System.Threading;
using LibVLCSharp.Shared;

namespace ScreenRecorderNetApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Core.Initialize();

            using (var libVlc = new LibVLC())
            using (var mediaPlayer = new MediaPlayer(libVlc))
            {
                var media = new Media(libVlc, "screen://", FromType.FromLocation);
                media.AddOption(":screen-fps=24");
                media.AddOption(":sout=#transcode{vcodec=h264,vb=0,scale=0,acodec=mp4a,ab=128,channels=2,samplerate=44100}:file{dst=testvlc.mp4}");
                media.AddOption(":sout-keep");

                mediaPlayer.Play(media);
                Thread.Sleep(10*1000);
                mediaPlayer.Stop();
            }
        }
    }
}

1
A chi potrebbe interessare: Nota che libvlc è rilasciato sotto licenza GPL. Se non si intende rilasciare il codice in GPL, non utilizzare libvlc.
Sebastian Cabot

Non è del tutto esatto, LibVLC è LGPL - è stato rilasciato in licenza nel 2011. L'applicazione VLC stessa rimane GPL: videolan.org/press/lgpl-libvlc.html
Andrew

0

DXGI Desktop Capture

Progetto che cattura l'immagine del desktop con la duplicazione DXGI. Salva l'immagine acquisita nel file in diversi formati di immagine (* .bmp; * .jpg; * .tif).

Questo esempio è scritto in C ++. È inoltre necessaria una certa esperienza con DirectX (D3D11, D2D1).

Cosa può fare l'applicazione

  • Se hai più di un monitor desktop, puoi scegliere.
  • Ridimensiona l'immagine desktop acquisita.
  • Scegli diverse modalità di ridimensionamento.
  • È possibile mostrare o nascondere l'icona del mouse nell'immagine di output.
  • È possibile ruotare l'immagine per l'immagine di output o lasciarla come predefinita.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.