178 votes

Méthode la plus rapide de capture d'écran

Je veux écrire un programme de screencasting pour la plateforme Windows, mais je ne sais pas comment capturer l'écran. La seule méthode dont j'ai connaissance est l'utilisation de GDI, mais je suis curieux de savoir s'il existe d'autres façons de procéder et, le cas échéant, laquelle entraîne le moins de frais généraux ? La vitesse est une priorité.

Le programme de screencasting servira à enregistrer les séquences de jeu, mais si cela réduit les options, je suis toujours ouvert à toute autre suggestion qui n'entre pas dans ce cadre. Le savoir n'est pas mauvais, après tout.

Modifier : Je suis tombé sur cet article : Différentes méthodes pour capturer l'écran . Il m'a fait découvrir la façon de procéder de l'API Windows Media et la façon de procéder de DirectX. Dans la conclusion, il est mentionné que la désactivation de l'accélération matérielle peut améliorer considérablement les performances de l'application de capture. Je suis curieux de savoir pourquoi. Quelqu'un pourrait-il remplir les blancs manquants pour moi ?

Modifier : J'ai lu que les programmes de screencasting tels que Camtasia utilisent leur propre pilote de capture. Quelqu'un pourrait-il m'expliquer en détail comment cela fonctionne, et pourquoi c'est plus rapide ? Je pourrais également avoir besoin de conseils sur la mise en œuvre de quelque chose comme ça, mais je suis sûr qu'il existe une documentation existante de toute façon.

Aussi, je sais maintenant comment FRAPS enregistre l'écran. Il utilise l'API graphique sous-jacente pour lire à partir du tampon arrière. D'après ce que j'ai compris, c'est plus rapide que de lire à partir du tampon avant, car vous lisez à partir de la RAM du système, plutôt que de la RAM vidéo. Vous pouvez lire l'article aquí .

0 votes

Avez-vous envisagé, plutôt que d'enregistrer graphiquement le contenu de l'écran, d'utiliser une système de relecture ?

0 votes

@PigBen C'était une lecture intéressante, mais je ne pense pas que ça marcherait. Il faudrait que j'accroche les événements d'une manière ou d'une autre, ce qui n'est pas faisable en utilisant une application générique, et il semble que je devrais faire un peu de piratage. Il en va de même pour le rendu.

2 votes

Vous n'avez pas besoin d'accrocher quoi que ce soit. Il suffit d'écrire vos événements d'entrée de manière à ce qu'ils ne contrôlent pas directement le jeu, mais appellent d'autres fonctions. Par exemple, si le joueur appuie sur la touche gauche, vous ne devez pas simplement décrémenter la position x du joueur. Au lieu de cela, vous appelez une fonction, comme MovePlayerLeft() . Et vous enregistrez également l'heure et la durée des pressions sur les touches et autres entrées. Ensuite, lorsque vous êtes en mode lecture, vous ignorez simplement l'entrée et vous lisez les données enregistrées. Si, dans les données, vous voyez une pression sur la touche gauche, vous appelez MovePlayerLeft() .

67voto

Brandrew Points 484

C'est ce que j'utilise pour collecter les images individuelles, mais si vous modifiez cette méthode et que vous gardez les deux cibles ouvertes tout le temps, vous pouvez les "streamer" sur le disque en utilisant un compteur statique pour le nom du fichier. - Je ne me souviens plus où j'ai trouvé ça, mais ça a été modifié, merci à qui de droit !

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

0 votes

Merci. Il y a quelque temps, j'ai entendu parler d'une méthode qui était censée être plus rapide que la lecture à partir du tampon avant. Est-ce que vous procédez honnêtement de cette façon et est-ce que cela fonctionne correctement ?

0 votes

Le problème avec le tampon frontal est un problème d'accès, c'est-à-dire qu'essayer de copier une plaine qui est en train d'être rendue "interrompt" la copie. Cela fonctionne assez bien pour moi et mange mon disque dur !

0 votes

@bobobo Je ne sais pas comment cela fonctionnerait exactement, mais je pensais utiliser quelque chose comme Huffyuv. Edit : Ou peut-être laisser l'utilisateur choisir parmi les filtres de directhow disponibles.

42voto

darvids0n Points 7914

EDIT : Je vois que cela est listé sous votre premier lien d'édition comme "la méthode GDI". C'est toujours une façon décente d'y aller, même avec l'avis de performance sur ce site, vous pouvez atteindre 30fps facilement je pense.

Desde este (Je n'ai aucune expérience dans ce domaine, je me réfère simplement à quelqu'un qui le fait) :

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

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

Je ne dis pas que c'est le plus rapide, mais le BitBlt est généralement très rapide si vous effectuez une copie entre des contextes de périphériques compatibles.

Pour référence, Logiciel de diffusion ouvert met en œuvre quelque chose comme cela dans le cadre de son "Capture de fenêtre" mais plutôt que de créer le contexte de destination hDest en utilisant CreateCompatibleDC ils utilisent un D3D10Texture .

Pour le modifier afin d'utiliser une application spécifique, vous devez changer la première ligne en GetDC(game)game est l'identifiant de la fenêtre du jeu, puis définissez le paramètre droit height y width de la fenêtre du jeu également.

Une fois que vous avez les pixels dans hDest/hbDesktop, vous avez toujours besoin de les enregistrer dans un fichier, mais si vous faites une capture d'écran, je pense que vous voudrez mettre en mémoire tampon un certain nombre d'entre eux et les enregistrer dans le fichier vidéo par morceaux, donc je n'indiquerai pas le code pour enregistrer une image statique sur le disque.

0 votes

Je pensais que la création d'un contexte de périphérique de mémoire avait pour but de réduire le scintillement (c'est-à-dire par le biais d'une double mise en mémoire tampon). Si vous n'utilisez pas CreateCompatibleDC, devra-t-il vraiment convertir entre les contextes (quel est cet autre contexte de toute façon) ? C'est juste quelque chose de trivial que je veux savoir.

3 votes

msdn.microsoft.com/fr/us/library/dd183370%28VS.85%29.aspx Extrait : Si les formats de couleur des contextes de périphérique source et de destination ne correspondent pas, la fonction BitBlt convertit le format de couleur source pour qu'il corresponde au format de destination.

2 votes

Des preuves à l'appui, ce serait bien. Avez-vous publié une comparaison des performances quelque part, ou en avez-vous vu une précise ?

24voto

Hernán Points 2452

J'ai écrit un logiciel de capture vidéo, similaire à FRAPS pour les applications DirectX. Le code source est disponible et mon article explique la technique générale. Regardez http://blog.nektra.com/main/2013/07/23/instrumenting-direct3d-applications-to-capture-video-and-calculate-frames-per-second/

Respect à vos questions relatives à la performance,

  • DirectX devrait être plus rapide que GDI, sauf lorsque vous lisez le frontbuffer, ce qui est très lent. Mon approche est similaire à FRAPS (lecture à partir du backbuffer). J'intercepte un ensemble de méthodes des interfaces Direct3D.

  • Pour l'enregistrement vidéo en temps réel (avec un impact minimal sur les applications), un codec rapide est essentiel. FRAPS utilise son propre codec vidéo sans perte. Lagarith et HUFFYUV sont des codecs vidéo sans perte génériques conçus pour les applications en temps réel. Vous devriez les regarder si vous voulez produire des fichiers vidéo.

  • Une autre approche de l'enregistrement de screencasts pourrait consister à écrire un pilote de miroir. Selon Wikipedia : Lorsque la mise en miroir vidéo est active, chaque fois que le système dessine sur le périphérique vidéo principal à un endroit situé dans la zone en miroir, une copie de l'opération de dessin est exécutée sur le périphérique vidéo en miroir en temps réel. Voir les pilotes de miroir sur MSDN : http://msdn.microsoft.com/en-us/library/Windows/hardware/ff568315(v=vs.85).aspx .

18voto

bobobobo Points 17477

J'utilise d3d9 pour obtenir le backbuffer, et je le sauvegarde dans un fichier png en utilisant la bibliothèque d3dx :

    IDirect3DSurface9 \*surface ;

    // [GetBackBuffer](http://msdn.microsoft.com/en-us/library/bb174379(VS.85).aspx)
    idirect3ddevice9->GetBackBuffer(0, 0, D3DBACKBUFFER\_TYPE\_MONO, &surface ) ;

    // save the surface
    D3DXSaveSurfaceToFileA( "filename.png", D3DXIFF\_PNG, surface, NULL, NULL ) ;

    SAFE\_RELEASE( surface ) ;

Pour ce faire, vous devez créer votre swapbuffer avec

d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(Ainsi vous garantissez que le backbuffer n'est pas endommagé avant de prendre la capture d'écran).

0 votes

C'est logique, merci. Savez-vous quelle est la différence entre ceci et GetRenderTarget est ?

0 votes

Cela permet d'obtenir la cible de rendu actuelle (il peut s'agir d'une autre surface hors écran si quelqu'un effectue un rendu de texture au moment où vous appelez).

11voto

Tedd Hansen Points 4893

Pour le C++, vous pouvez utiliser : http://www.pinvoke.net/default.aspx/gdi32/BitBlt.html
Il se peut que cela ne fonctionne pas avec tous les types d'applications 3D/applications vidéo. Alors ce lien peut être plus utile car il décrit 3 méthodes différentes que vous pouvez utiliser.

Ancienne réponse (C#) :
Vous pouvez utiliser System.Drawing.Graphics.Copy mais il n'est pas très rapide.

Un exemple de projet que j'ai écrit fait exactement cela : http://blog.tedd.no/index.php/2010/08/16/c-image-analysis-auto-gaming-with-source/

Je prévois de mettre à jour cet échantillon en utilisant une méthode plus rapide comme Direct3D : http://spazzarama.wordpress.com/2009/02/07/screencapture-with-direct3d/

Et voici un lien pour capturer en vidéo : Comment capturer l'écran pour en faire une vidéo en utilisant C# .Net ?

2 votes

Ah, j'ai oublié de préciser que je programme en C (éventuellement en C++) et que je ne prévois pas d'utiliser .NET. Terriblement désolé :/.

0 votes

Je connaissais déjà BitBlt (GDI). Je vais cependant me pencher sur Direct3D. Merci !

0 votes

Je me suis penché sur cette question il y a quelques semaines, mais je n'ai pas encore réussi à la mettre en œuvre. Direct3D est bien plus rapide que la méthode intégrée de C# qui utilise GDI+.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X