El método más rápido de captura de pantalla en Windows

159

Quiero escribir un programa de screencasting para la plataforma Windows, pero no estoy seguro de cómo capturar la pantalla. El único método que conozco es usar GDI, pero tengo curiosidad por saber si hay otras formas de hacerlo, y si las hay, ¿cuál incurre en la menor sobrecarga? La velocidad es una prioridad.

El programa de screencasting será para grabar imágenes del juego, aunque, si esto reduce las opciones, todavía estoy abierto a cualquier otra sugerencia que quede fuera de este alcance. El conocimiento no es malo, después de todo.

Editar : Me encontré con este artículo: Varios métodos para capturar la pantalla . Me ha presentado la forma de hacerlo de la API de Windows Media y la forma de hacerlo de DirectX. En la conclusión se menciona que deshabilitar la aceleración de hardware podría mejorar drásticamente el rendimiento de la aplicación de captura. Tengo curiosidad de por qué es esto. ¿Alguien podría completar los espacios en blanco que faltan por mí?

Editar : leí que los programas de captura de pantalla como Camtasia usan su propio controlador de captura. ¿Podría alguien darme una explicación detallada sobre cómo funciona y por qué es más rápido? También es posible que necesite orientación para implementar algo así, pero estoy seguro de que hay documentación existente de todos modos.

Además, ahora sé cómo FRAPS graba la pantalla. Engancha la API de gráficos subyacente para leer desde el búfer posterior. Por lo que entiendo, esto es más rápido que leer desde el búfer frontal, porque estás leyendo desde la RAM del sistema, en lugar de la RAM de video. Puedes leer el artículo aquí .

algún chico
fuente
¿Ha considerado, en lugar de grabar gráficamente el contenido de la pantalla, usar un sistema de reproducción ?
Benjamin Lindley
2
No tienes que enganchar nada. Solo tienes que escribir tus eventos de entrada para que no controlen el juego directamente, sino que llamen a otras funciones. Por ejemplo, si el jugador presiona la tecla izquierda, no simplemente disminuye la posición x de los jugadores. En cambio, llamas a una función, como MovePlayerLeft(). Y también registra el tiempo y la duración de las pulsaciones de teclas y otras entradas. Luego, cuando está en modo de reproducción, simplemente ignora la entrada y lee los datos grabados. Si, en los datos, ve una tecla izquierda presionada, llama MovePlayerLeft().
Benjamin Lindley
1
@PigBen Esta será una aplicación genérica para grabar imágenes de juegos. No es para un juego específico. Alguien que presiona la tecla izquierda podría significar moverse hacia la derecha, por lo que sé. Además, no ha considerado eventos que no están influenciados por el usuario. ¿Y qué hay del renderizado?
someguy
1
@someguy Ok, supongo que estás haciendo algo mucho más intenso, agregué una rutina usando los métodos anteriores para ahorrar la repetición de AVI en un juego a alrededor de 30 fps sin problemas. Hice una grabadora de pantalla de múltiples monitores usando la API de Windows para la "optimización de la fuerza de trabajo", pero funcionó mal incluso a 4 fps específicos.
AJG85
1
Hay un controlador espejo de código abierto para Windows en el sitio del repositorio de UltraVNC aquí ultravnc.svn.sourceforge.net/viewvc/ultravnc/…
Beached

Respuestas:

62

Esto es lo que uso para recopilar fotogramas individuales, pero si modifica esto y mantiene los dos objetivos abiertos todo el tiempo, entonces podría "transmitirlo" al disco usando un contador estático para el nombre del archivo. - No recuerdo dónde encontré esto, pero se ha modificado, ¡gracias a quien sea!

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();
}
Brandrew
fuente
Gracias. Hace un tiempo, escuché sobre este método que se decía que era más rápido que leer desde el búfer frontal. ¿Honestamente lo haces de esa manera y funciona correctamente?
someguy
El problema con el búfer frontal es de acceso, es decir, tratar de copiar un plano que actualmente se representa "interrumpe" la copia. ¡Funciona lo suficientemente bien para mí y se come mi disco duro!
Brandrew
@bobobobo No sé cómo funcionaría exactamente, pero estaba pensando en usar algo como Huffyuv. Editar: O tal vez permita que el usuario elija entre los filtros de visualización directa disponibles.
someguy
1
Simplemente no puedo hacer que eso funcione ... DirectX escupe algunas llamadas no válidas en la parte GetRenderTargetData. Obviamente, la forma en que crea su dispositivo debe tener mucha importancia.
LightStriker
12
voto negativo, solo funciona para su propia aplicación, por lo que no se puede usar para grabar programas genéricos
user3125280
32

EDITAR: puedo ver que esto aparece en su primer enlace de edición como "la forma GDI". Esta sigue siendo una forma decente, incluso con el aviso de rendimiento en ese sitio, creo que puede llegar a 30 fps fácilmente.

De este comentario (no tengo experiencia haciendo esto, solo estoy haciendo referencia a alguien que sí lo hace):

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

No digo que sea el más rápido, pero la BitBltoperación generalmente es muy rápida si está copiando entre contextos de dispositivos compatibles.

Como referencia, Open Broadcaster Software implementa algo como esto como parte de su método "dc_capture" , aunque en lugar de crear el contexto de destino hDestmediante el CreateCompatibleDCuso de un IDXGISurface1, que funciona con DirectX 10+. Si no hay apoyo para esto, recurren a CreateCompatibleDC.

Para cambiarlo para usar una aplicación específica, debes cambiar la primera línea a GetDC(game)donde gameestá el identificador de la ventana del juego, y luego establecer la derecha heighty widthla ventana del juego también.

Una vez que tenga los píxeles en hDest / hbDesktop, aún debe guardarlos en un archivo, pero si está haciendo una captura de pantalla, creo que querría almacenar un cierto número de ellos en la memoria y guardarlos en el archivo de video en fragmentos, por lo que no señalaré el código para guardar una imagen estática en el disco.


fuente
3
msdn.microsoft.com/en-us/library/dd183370%28VS.85%29.aspx Extracto: si los formatos de color de los contextos de origen y destino del dispositivo no coinciden, la función BitBlt convierte el formato de color de origen para que coincida con el destino formato.
2
Alguna evidencia para corroborar que sería bueno. ¿Has publicado una comparación de rendimiento en alguna parte o has visto una precisa?
2
Intenta perfilarlo. Como dije en la publicación, estoy haciendo referencia a alguien que tiene experiencia con GDI. Si pierde memoria y sabe cómo solucionarlo, edite la publicación para eliminar la pérdida.
1
Obtuve unos 5 fps con este método. Esto fue en una computadora portátil con gráficos integrados, por lo que esperaría mejor de una computadora de escritorio con una tarjeta gráfica real, pero aún así, es muy lenta.
Timmmm
1
@Timmmm He agregado una referencia a la forma en que OBS implementa esto. Espero que eso pueda acelerar un poco las cosas para ti.
19

Escribí un software de captura de video, similar a FRAPS para aplicaciones DirectX. El código fuente está disponible y mi artículo explica la técnica general. Mire http://blog.nektra.com/main/2013/07/23/instrumenting-direct3d-applications-to-capture-video-and-calculate-frames-per-second/

Respeto a sus preguntas relacionadas con el rendimiento,

  • DirectX debería ser más rápido que GDI, excepto cuando está leyendo desde el búfer frontal, que es muy lento. Mi enfoque es similar a FRAPS (lectura de backbuffer). Intercepto un conjunto de métodos de las interfaces Direct3D.

  • Para la grabación de video en tiempo real (con un impacto mínimo en la aplicación), es esencial un códec rápido. FRAPS usa su propio códec de video sin pérdidas. Lagarith y HUFFYUV son códecs de video genéricos sin pérdidas diseñados para aplicaciones en tiempo real. Debería mirarlos si desea generar archivos de video.

  • Otro enfoque para grabar screencasts podría ser escribir un Driver Mirror. Según Wikipedia: cuando la duplicación de video está activa, cada vez que el sistema se dirige al dispositivo de video primario en una ubicación dentro del área reflejada, se ejecuta una copia de la operación de dibujo en el dispositivo de video reflejado en tiempo real. Vea los controladores espejo en MSDN: http://msdn.microsoft.com/en-us/library/windows/hardware/ff568315(v=vs.85).aspx .

Hernán
fuente
16

Uso d3d9 para obtener el backbuffer, y lo guardo en un archivo png usando la biblioteca d3dx:

    IDirect3DSurface9 * superficie;

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

    // guardar la superficie
    D3DXSaveSurfaceToFileA ("filename.png", D3DXIFF_PNG, superficie, NULL, NULL);

    SAFE_RELEASE (superficie);

Para hacer esto, debes crear tu swapbuffer con

d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(Por lo tanto, usted garantiza que el backbuffer no está dañado antes de tomar la captura de pantalla).

bobobobo
fuente
Tiene sentido, gracias. ¿Sabes cuál es la diferencia entre esto y lo que GetRenderTargetes?
someguy
Eso solo obtiene el objetivo de renderizado actual (podría ser otra superficie fuera de la pantalla si alguien está renderizando a la textura en el momento en que llama).
bobobobo
13

En mi impresión, el enfoque GDI y el enfoque DX son diferentes en su naturaleza. pintar usando GDI aplica el método FLUSH, el enfoque FLUSH dibuja el cuadro, luego lo borra y vuelve a dibujar otro cuadro en el mismo búfer, esto dará como resultado que el parpadeo en los juegos requiera una alta velocidad de cuadros.

  1. ¿POR QUÉ DX más rápido? en DX (o mundo de gráficos), se aplica un método más maduro llamado renderizado de doble buffer, donde hay dos buffers, cuando se presenta el buffer frontal al hardware, también se puede renderizar al otro buffer, luego, después de que el cuadro 1 está finalizado el renderizado, el sistema cambia al otro búfer (bloqueándolo para presentarlo al hardware y liberando el búfer anterior), de esta forma la ineficiencia del renderizado se mejora enormemente.
  2. ¿POR QUÉ bajar la aceleración de hardware más rápido? aunque con la representación de doble búfer, el FPS se mejora, pero el tiempo para la representación aún es limitado. el hardware gráfico moderno generalmente implica una gran optimización durante el renderizado, generalmente como anti-aliasing, esto requiere mucha computación, si no requiere gráficos de alta calidad, por supuesto, simplemente puede deshabilitar esta opción. y esto te ahorrará algo de tiempo.

Creo que lo que realmente necesitas es un sistema de reproducción, que estoy totalmente de acuerdo con lo que la gente discutió.

zinc
fuente
1
Vea la discusión sobre por qué un sistema de repetición no es factible. El programa de screencasting no es para ningún juego específico.
someguy
10

Escribí una clase que implementó el método GDI para la captura de pantalla. Yo también quería velocidad adicional, así que después de descubrir el método DirectX (a través de GetFrontBuffer) lo intenté, esperando que fuera más rápido.

Me consternó descubrir que GDI funciona aproximadamente 2.5 veces más rápido. Después de 100 intentos de capturar mi monitor dual, la implementación de GDI promedió 0.65 segundos por captura de pantalla, mientras que el método DirectX promedió 1.72 segundos. Entonces, GDI es definitivamente más rápido que GetFrontBuffer, según mis pruebas.

No pude hacer que el código de Brandrew funcionara para probar DirectX a través de GetRenderTargetData. La copia de la pantalla salió puramente negra. Sin embargo, ¡podría copiar esa pantalla en blanco súper rápido! Seguiré jugando con eso y espero obtener una versión que funcione para ver resultados reales.

rotanimod
fuente
Gracias por la información. No he probado el código de Brandrew, pero sé que tomar el GetRenderTargetDataenfoque funciona. Tal vez escriba mi propia respuesta cuando termine mi solicitud. O bien, puede actualizar el suyo una vez que tenga todo funcionando.
someguy
0.65 por captura de pantalla ?! Una buena implementación de GDI (mantener dispositivos cerca, etc.) debería hacer 30 fps en 1920x1200 fácilmente en una computadora moderna.
Christopher Oezbek
Supongo que la calidad de imagen
generada
He realizado esta prueba en C # con SlimDX y, sorprendentemente, encontré los mismos resultados. Quizás esto tenga que ver con el hecho de que, usando SlimDX, uno tiene que crear una nueva secuencia y un nuevo mapa de bits para cada actualización de cuadro, en lugar de crearlo una vez, rebobinar y sobrescribir la misma ubicación.
Cesar
Solo una aclaración: en realidad, los "mismos resultados" se referían a que GDI era más rápido, como mencionó @Christopher, 30 fps + era muy factible y todavía dejaba un montón de CPU de repuesto.
Cesar
8

Algunas cosas que he podido deducir: aparentemente usar un "controlador espejo" es rápido, aunque no conozco un OSS.

¿Por qué RDP es tan rápido en comparación con otro software de control remoto?

También, aparentemente, usar algunas convoluciones de StretchRect es más rápido que BitBlt

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

Y el que mencionó (fraps enganchando a las D3D dll) es probablemente la única forma para las aplicaciones D3D, pero no funcionará con la captura de escritorio de Windows XP. Así que ahora solo desearía que hubiera un fraps equivalente en velocidad para las ventanas de escritorio normales ... ¿alguien?

(Creo que con aero podría usar ganchos tipo fraps, pero los usuarios de XP no tendrían suerte).

Aparentemente, también cambia la profundidad de bits de la pantalla y / o deshabilita la aceleración de hardware. podría ayudar (y / o deshabilitar aero).

https://github.com/rdp/screen-capture-recorder-program incluye una utilidad de captura basada en BitBlt razonablemente rápida y un punto de referencia como parte de su instalación, que puede permitirle comparar las velocidades de BitBlt para optimizarlas.

VirtualDub también tiene un módulo de captura de pantalla "opengl" que se dice que es rápido y hace cosas como detección de cambios http://www.virtualdub.org/blog/pivot/entry.php?id=290

rogerdpack
fuente
Me pregunto, ¿será más rápido usar tu "capturador de pantalla" o usar BitBlt yo mismo? ¿Hay alguna optimización en su proyecto?
blez
Si usa Bitblt usted mismo, podría evitar un memcpy adicional (memcpy generalmente no es el mayor cuello de botella, aunque agrega algo de tiempo, solo lo mencioné aquí por su utilidad de referencia, pero si necesita algo o forma directa, entonces es agradable )
rogerdpack
8

Para C ++ puede usar: http://www.pinvoke.net/default.aspx/gdi32/BitBlt.html
Esto puede no funcionar en todos los tipos de aplicaciones 3D / aplicaciones de video. Entonces este enlace puede ser más útil, ya que describe 3 métodos diferentes que puede usar.

Respuesta anterior (C #):
puede usar System.Drawing.Graphics.Copy , pero no es muy rápido.

Un proyecto de muestra que escribí haciendo exactamente esto: http://blog.tedd.no/index.php/2010/08/16/c-image-analysis-auto-gaming-with-source/

Estoy planeando actualizar esta muestra usando un método más rápido como Direct3D: http://spazzarama.com/2009/02/07/screencapture-with-direct3d/

Y aquí hay un enlace para capturar video: ¿Cómo capturar la pantalla para que sea video usando C # .Net?

Tedd Hansen
fuente
2
Ah, olvidé mencionar que estoy programando en C (posiblemente C ++) y no estoy planeando usar .NET. Lo siento mucho :/.
someguy
Ya estaba al tanto de BitBlt (GDI). Sin embargo, investigaré Direct3D. ¡Gracias!
someguy
Estuve investigando esto hace unas semanas, pero aún no he podido implementarlo. ¡Direct3D es !! más rápido que el método integrado C # que usa GDI +.
Tedd Hansen
He actualizado mi publicación original. Según el enlace, DirectX es lento cuando tiene que llamar a GetFrontBufferData (). ¿Es esto algo a tener en cuenta al grabar imágenes del juego? ¿Podrías contextualizar esto para mí?
someguy
1
GDI es lento, por lo que no es adecuado para el dominio del problema, DirectX u OpenGL sería la única recomendación sensata.
6

Con Windows 8, Microsoft presentó la API de duplicación de escritorio de Windows. Esa es la forma oficialmente recomendada de hacerlo. Una buena característica que tiene para el screencasting es que detecta el movimiento de la ventana, por lo que puede transmitir deltas de bloque cuando las ventanas se mueven, en lugar de píxeles sin formato. Además, le indica qué rectángulos han cambiado, de un cuadro al siguiente.

El código de ejemplo de Microsoft es bastante complejo, pero la API es realmente simple y fácil de usar. He reunido un proyecto de ejemplo que es mucho más simple que el ejemplo oficial:

https://github.com/bmharper/WindowsDesktopDuplicationSample

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

Código de ejemplo oficial de Microsoft: https://code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a

Ben Harper
fuente
El proyecto Github funciona perfectamente en Windows 10, probado con video web y Resident Evil 7. Lo mejor es que la carga de la CPU se escala con la tasa de actualización de gráficos.
jw_
tan pronto como inicie GPU-Z, este programa deja de tomar la pantalla.
Marino Šimić
5

Puede probar el proyecto de código abierto de c ++ WinRobot @git , un potente capturador de pantalla

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;

Apoyo:

  • Ventana UAC
  • Winlogon
  • DirectShowOverlay
Caimán
fuente
1
Eso es ... muchos ganchos de bajo nivel. La velocidad de captura es asombrosa si tienes derechos de administrador.
toster-cx
Encontré el winrobot muy poderoso y suave.
ashishgupta_mca
Estudié el código de WinRobot y no vi nada innovador en la captura de pantalla de wrt: está usando el mismo CreateCompatibleDC..BitBlt. ¿A menos que haya algo de magia cuando esto se realiza en el contexto del servicio?
shekh
@ shekh: Tu estudio fue demasiado superficial. El código usa IDirectDrawSurface7-> BltFast () para copiar la pantalla desde la superficie de dibujo de la pantalla a una superficie de copia DD, luego usa un Filemapping para copiar la imagen. Es bastante complejo porque el código se ejecuta en un servicio donde no se puede acceder fácilmente a los escritorios.
Elmue
2

Yo mismo lo hago con directx y creo que es tan rápido como te gustaría que fuera. No tengo un ejemplo de código rápido, pero encontré esto que debería ser útil. la versión de directx11 no debería diferir mucho, directx9 quizás un poco más, pero ese es el camino a seguir

cppanda
fuente
2

Me doy cuenta de que la siguiente sugerencia no responde a su pregunta, pero el método más simple que he encontrado para capturar una vista DirectX que cambia rápidamente, es conectar una cámara de video al puerto S-video de la tarjeta de video y grabar las imágenes como una película. Luego transfiera el video de la cámara a un archivo MPG, WMV, AVI, etc. en la computadora.

Pierre
fuente
2

La grabación de pantalla se puede hacer en C # usando la API VLC . He hecho un programa de muestra para demostrar esto. Utiliza LibVLCSharp y VideoLAN.LibVLC.Windows bibliotecas . Podría lograr muchas más funciones relacionadas con la representación de video utilizando esta API multiplataforma.

Para ver la documentación de la API, consulte: 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();
            }
        }
    }
}
Karthick
fuente
1
A quien corresponda: Tenga en cuenta que libvlc se publica bajo GPL. Si no tiene la intención de liberar su código bajo GPL, entonces no use libvlc.
Sebastian Cabot
Eso no es del todo exacto, LibVLC es LGPL: se volvió a licenciar en 2011. La aplicación VLC en sí misma sigue siendo GPL: videolan.org/press/lgpl-libvlc.html
Andrew
0

Captura de escritorio DXGI

Proyecto que captura la imagen del escritorio con duplicación DXGI. Guarda la imagen capturada en el archivo en diferentes formatos de imagen (* .bmp; * .jpg; * .tif).

Esta muestra está escrita en C ++. También necesita algo de experiencia con DirectX (D3D11, D2D1).

Lo que puede hacer la aplicación

  • Si tiene más de un monitor de escritorio, puede elegir.
  • Cambiar el tamaño de la imagen de escritorio capturada.
  • Elija diferentes modos de escala.
  • Puede mostrar u ocultar el icono del mouse en la imagen de salida.
  • Puede girar la imagen para la imagen de salida o dejarla como predeterminada.
gerdogdu
fuente