Método mais rápido de captura de tela no Windows

159

Quero escrever um programa de captura de tela para a plataforma Windows, mas não tenho certeza de como capturar a tela. O único método que eu conheço é usar o GDI, mas estou curioso para saber se existem outras maneiras de fazer isso e, se houver, o que implica menos sobrecarga? A velocidade é uma prioridade.

O programa de screencasting será para a gravação de cenas de jogos, embora, se isso restringir as opções, ainda estou aberto a outras sugestões que se enquadram nesse escopo. Afinal, o conhecimento não é ruim.

Edit : Me deparei com este artigo: Vários métodos para capturar a tela . Ele me apresentou a maneira de fazer a API do Windows Media e a maneira de fazer o DirectX. Menciona na conclusão que desabilitar a aceleração de hardware pode melhorar drasticamente o desempenho do aplicativo de captura. Estou curioso para saber por que isso acontece. Alguém poderia preencher os espaços em branco para mim?

Edit : Eu li que programas de screencasting como o Camtasia usam seu próprio driver de captura. Alguém poderia me dar uma explicação detalhada sobre como funciona e por que é mais rápido? Também posso precisar de orientações sobre como implementar algo assim, mas tenho certeza de que existe documentação existente de qualquer maneira.

Além disso, agora eu sei como o FRAPS grava a tela. Ele conecta a API gráfica subjacente para ler a partir do buffer traseiro. Pelo que entendi, isso é mais rápido do que ler no buffer frontal, porque você está lendo na RAM do sistema, em vez da RAM de vídeo. Você pode ler o artigo aqui .

someguy
fonte
Você considerou, em vez de gravar graficamente o conteúdo da tela, usando um sistema de reprodução ?
Benjamin Lindley
2
Você não precisa ligar nada. Você apenas precisa escrever seus eventos de entrada para que eles não controlem o jogo diretamente, mas chame outras funções. Por exemplo, se o jogador pressiona a tecla esquerda, você simplesmente não diminui a posição x do jogador. Em vez disso, você chama uma função, como MovePlayerLeft(). E você também grava a hora e a duração das teclas pressionadas e outras entradas. Então, quando você está no modo de reprodução, simplesmente ignora a entrada e lê os dados gravados. Se, nos dados, você pressionar a tecla esquerda, ligue MovePlayerLeft().
Benjamin Lindley
1
@PigBen Esta será uma aplicação genérica para gravar imagens de jogos. Não é para um jogo específico. Alguém pressionando a tecla esquerda pode significar se mover para a direita, pelo que sei. Além disso, você não considerou eventos que não são influenciados pelo usuário. 'E quanto à renderização?
someguy
1
@someguy Ok, eu acho que você está fazendo algo muito mais intenso. Eu adicionei uma rotina usando os métodos acima para evitar a reprodução de AVIs em um jogo a cerca de 30fps sem problemas. Criei um gravador de tela de vários monitores usando a API do Windows para "otimização da força de trabalho", mas que teve um desempenho ruim, mesmo em 4fps direcionados.
AJG85
1
Existe um driver de espelho de código aberto para Windows no site do repositório do UltraVNC, aqui ultravnc.svn.sourceforge.net/viewvc/ultravnc/…
Encalhado

Respostas:

62

É isso que eu uso para coletar quadros únicos, mas se você modificar isso e manter os dois destinos abertos o tempo todo, poderá "transmiti-lo" para o disco usando um contador estático para o nome do arquivo. - Não me lembro onde encontrei isso, mas foi modificado, graças a quem!

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
fonte
Obrigado. Ouvi falar sobre esse método há um tempo atrás, que era mais rápido do que ler no buffer frontal. Você honestamente faz dessa maneira e funciona corretamente?
someguy
O problema com o buffer frontal é o acesso, ou seja, tentar copiar uma planície que está sendo renderizada atualmente "interrompe" a cópia. Funciona bem o suficiente para mim e come meu disco rígido!
Brandrew 28/02
@obobobo Eu não sei exatamente como isso funcionaria, mas eu estava pensando em usar algo como o Huffyuv. Editar: ou talvez deixe o usuário escolher entre os filtros de exibição direta disponíveis.
someguy
1
Eu simplesmente não consigo fazer isso funcionar ... O DirectX cospe alguma chamada inválida na parte GetRenderTargetData. Obviamente, a maneira como você cria seu dispositivo deve ter muita importância.
LightStriker
12
downvote, ele só funciona para a sua própria aplicação, então não pode ser usado para gravar programas genéricos
user3125280
32

Edição: Eu posso ver que isso está listado no seu primeiro link de edição como "o caminho GDI". Esse ainda é um caminho decente, mesmo com o aviso de desempenho nesse site; você pode chegar a 30fps facilmente, eu acho.

A partir deste comentário (não tenho experiência em fazer isso, estou apenas referenciando alguém que faz):

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

Não estou dizendo que esse é o mais rápido, mas a BitBltoperação geralmente é muito rápida se você estiver copiando entre contextos de dispositivos compatíveis.

Para referência, Abrir Broadcaster Software implementa algo como isso como parte de sua "dc_capture" método, embora em vez de criar o contexto de destino hDestusando CreateCompatibleDCeles usam um IDXGISurface1, que trabalha com DirectX 10 +. Se não houver suporte para isso, eles retornam ao CreateCompatibleDC.

Para alterá-lo para usar um aplicativo específico, você precisa alterar a primeira linha para GetDC(game)onde gameestá a alça da janela do jogo e, em seguida, definir a direita heighte widtha janela do jogo também.

Depois de ter os pixels no hDest / hbDesktop, você ainda precisará salvá-lo em um arquivo, mas se estiver fazendo captura de tela, acho que você gostaria de armazenar em buffer um certo número deles na memória e salvá-lo no arquivo de vídeo em pedaços, por isso não apontarei para o código para salvar uma imagem estática no disco.


fonte
3
msdn.microsoft.com/en-us/library/dd183370%28VS.85%29.aspx Trecho: se os formatos de cores dos contextos de dispositivo de origem e destino não corresponderem, a função BitBlt converterá o formato de cor de origem para corresponder ao destino formato.
2
Alguma evidência para comprovar que isso seria bom. Você publicou uma comparação de desempenho em algum lugar ou viu uma precisa?
2
Tente criar um perfil. Como eu disse no post, estou fazendo referência a alguém que tenha experiência com GDI. Se houver vazamento de memória e você souber como corrigi-lo, edite a postagem para eliminar o vazamento.
1
Eu tenho cerca de 5 qps com esse método. Este estava em um laptop com gráficos integrados, então eu esperaria melhor de um desktop com uma placa gráfica real, mas ainda assim, é muito lento.
Timmmm 18/09/2013
1
@ Timmmm Adicionei uma referência à maneira como o OBS implementa isso. Espero que isso acelere um pouco as coisas para você.
19

Eu escrevi um software de captura de vídeo, semelhante ao FRAPS para aplicativos DirectX. O código fonte está disponível e meu artigo explica a técnica geral. Veja http://blog.nektra.com/main/2013/07/23/instrumenting-direct3d-applications-to-capture-video-and-calculate-frames-per-second/

Respeite suas perguntas relacionadas ao desempenho,

  • O DirectX deve ser mais rápido que o GDI, exceto quando você está lendo no buffer frontal, que é muito lento. Minha abordagem é semelhante à FRAPS (leitura do backbuffer). Intercepto um conjunto de métodos das interfaces do Direct3D.

  • Para gravação de vídeo em tempo real (com impacto mínimo no aplicativo), um codec rápido é essencial. O FRAPS usa seu próprio codec de vídeo sem perdas. Lagarith e HUFFYUV são codecs de vídeo genéricos sem perdas, projetados para aplicativos em tempo real. Você deve olhar para eles se quiser gerar arquivos de vídeo.

  • Outra abordagem para gravar screencasts pode ser escrever um driver de espelho. De acordo com a Wikipedia: Quando o espelhamento de vídeo está ativo, cada vez que o sistema desenha no dispositivo de vídeo principal em um local dentro da área espelhada, uma cópia da operação de desenho é executada no dispositivo de vídeo espelhado em tempo real. Consulte os drivers de espelho no MSDN: http://msdn.microsoft.com/en-us/library/windows/hardware/ff568315(v=vs.85).aspx .

Hernán
fonte
16

Eu uso o d3d9 para obter o backbuffer e salve-o em um arquivo png usando a biblioteca d3dx:

    Superfície IDirect3DSurface9 *;

    // GetBackBuffer
    idirect3ddevice9-> GetBackBuffer (0, 0, D3DBACKBUFFER_TYPE_MONO, e superfície);

    // salva a superfície
    D3DXSaveSurfaceToFileA ("filename.png", D3DXIFF_PNG, superfície, NULL, NULL);

    SAFE_RELEASE (superfície);

Para fazer isso, você deve criar seu swapbuffer com

d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(Portanto, você garante que o backbuffer não seja mutilado antes de fazer a captura de tela).

bobobobo
fonte
Faz sentido, obrigado. Você sabe qual é a diferença entre isso e GetRenderTargeté?
someguy
Isso apenas obtém o destino de renderização atual (pode ser outra superfície fora da tela se alguém estiver renderizando a textura no momento em que você ligar).
22911 bobobobo
13

Na minha impressão, a abordagem GDI e a abordagem DX são diferentes em sua natureza. a pintura usando GDI aplica o método FLUSH, a abordagem FLUSH desenha o quadro, limpe-o e redesenhe outro quadro no mesmo buffer, isso resultará em tremulação nos jogos que exigem alta taxa de quadros.

  1. POR QUE DX é mais rápido? no DX (ou mundo dos gráficos), é aplicado um método mais maduro chamado renderização de buffer duplo, em que dois buffers estão presentes, quando o buffer frontal é apresentado ao hardware, você também pode renderizar para o outro buffer e depois o quadro 1 é finalizada a renderização, o sistema troca para o outro buffer (bloqueando-o para apresentação no hardware e liberando o buffer anterior), dessa maneira a ineficiência na renderização é bastante aprimorada.
  2. POR QUE desligar a aceleração de hardware mais rapidamente? embora com renderização de buffer duplo, o FPS seja aprimorado, mas o tempo para renderização ainda é limitado. o hardware gráfico moderno geralmente envolve muita otimização durante a renderização, normalmente como anti-aliasing, isso exige muita computação, se você não precisar de gráficos de alta qualidade, é claro que você pode simplesmente desativar essa opção. e isso economizará algum tempo.

Eu acho que o que você realmente precisa é de um sistema de repetição, que eu concordo totalmente com o que as pessoas discutiram.

zinking
fonte
1
Veja a discussão sobre por que um sistema de reprodução não é viável. O programa de screencasting não é para nenhum jogo específico.
someguy
10

Eu escrevi uma classe que implementou o método GDI para captura de tela. Eu também queria velocidade extra, então, depois de descobrir o método DirectX (via GetFrontBuffer), tentei isso, esperando que fosse mais rápido.

Fiquei consternado ao descobrir que o GDI executa cerca de 2,5 vezes mais rápido. Após 100 tentativas de captura do meu monitor de dois monitores, a implementação do GDI teve uma média de 0,65s por captura de tela, enquanto o método DirectX obteve uma média de 1,72s. Portanto, o GDI é definitivamente mais rápido que o GetFrontBuffer, de acordo com meus testes.

Não consegui que o código de Brandrew funcionasse para testar o DirectX via GetRenderTargetData. A cópia da tela saiu totalmente preta. No entanto, ele pode copiar a tela em branco super rápido! Vou continuar mexendo nisso e espero obter uma versão funcional para ver resultados reais.

rotanimod
fonte
Obrigado pela informação. Não testei o código de Brandrew, mas sei que adotar a GetRenderTargetDataabordagem funciona. Talvez eu escreva minha própria resposta quando terminar minha inscrição. Ou você pode atualizar o seu assim que tiver conseguido tudo funcionar.
someguy
0,65 por captura de tela ?! Uma boa implementação de GDI (mantendo os dispositivos por perto, etc.) deve fazer 30fps em 1920x1200 facilmente em um computador moderno.
Christopher Oezbek
Presumo que a qualidade de imagem renderizada por GDI é definitivamente mais pobre do que DX
zinking
Eu realizei este teste em C # com SlimDX e, surpreendentemente, encontrei os mesmos resultados. Talvez isso tenha a ver com o fato de que, usando o SlimDX, é necessário criar um novo fluxo e um novo bitmap para cada atualização de quadro, em vez de criá-lo uma vez, rebobinar e continuar substituindo o mesmo local.
Cesar
Apenas um esclarecimento: na verdade, os "mesmos resultados" estavam se referindo à GDI sendo mais rápida - como o @Christopher mencionou, 30fps + era muito factível e ainda deixava bastante CPU sobressalente.
Cesar
8

Algumas coisas que consegui entender: aparentemente, o uso de um "driver de espelho" é rápido, embora eu não esteja ciente de um OSS.

Por que o RDP é tão rápido em comparação com outros softwares de controle remoto?

Aparentemente, também o uso de algumas convoluções do StretchRect é mais rápido que o BitBlt

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

E o que você mencionou (fraps conectando-se às DLLs D3D) é provavelmente o único caminho para aplicativos D3D, mas não funcionará com a captura de área de trabalho do Windows XP. Então agora eu só queria que houvesse um equivalente fraps em velocidade para janelas normais de desktop ... alguém?

(Eu acho que com o aero você pode usar ganchos do tipo fraps, mas os usuários do XP ficariam sem sorte).

Aparentemente, também alterando a profundidade dos bits da tela e / ou desativando a aceleração do hardware. pode ajudar (e / ou desativar o Aero).

O https://github.com/rdp/screen-capture-recorder-program inclui um utilitário de captura baseado em BitBlt razoavelmente rápido e um benchmarker como parte de sua instalação, que permite comparar as velocidades de BitBlt para otimizá-las.

O VirtualDub também possui um módulo de captura de tela "opengl", que é rápido e funciona como detecção de alterações http://www.virtualdub.org/blog/pivot/entry.php?id=290

rogerdpack
fonte
Será que será mais rápido usar o seu "gravador de captura de tela" ou usar o BitBlt eu mesmo? Existe alguma otimização em seu projeto?
blez
Se você usar o Bitblt você mesmo, poderá evitar um memcpy extra (o memcpy normalmente não é o maior gargalo, embora isso inclua algum tempo - eu só o mencionei aqui por seu utilitário de benchmark, mas se você precisar de algo ou direcionar, então é bom )
rogerdpack
8

Para C ++, você pode usar: http://www.pinvoke.net/default.aspx/gdi32/BitBlt.html
Isso pode não funcionar em todos os tipos de aplicativos 3D / aplicativos de vídeo. Então este link pode ser mais útil, pois descreve três métodos diferentes que você pode usar.

Resposta antiga (C #):
Você pode usar System.Drawing.Graphics.Copy , mas não é muito rápido.

Um projeto de amostra que escrevi fazendo exatamente isso: http://blog.tedd.no/index.php/2010/08/16/c-image-analysis-auto-gaming-with-source/

Estou planejando atualizar esse exemplo usando um método mais rápido como o Direct3D: http://spazzarama.com/2009/02/07/screencapture-with-direct3d/

E aqui está um link para capturar em vídeo: Como capturar tela para ser vídeo usando C # .Net?

Tedd Hansen
fonte
2
Ah, esqueci de mencionar que estou programando em C (possivelmente C ++) e não pretendo usar o .NET. Terrivelmente arrependido :/.
someguy
Eu já estava ciente do BitBlt (GDI). Vou dar uma olhada no Direct3D, no entanto. Obrigado!
someguy
Eu estava analisando isso há algumas semanas, mas ainda não consegui implementá-lo. Direct3D é !! caminho !! mais rápido que o método C # interno que está usando GDI +.
Tedd Hansen
Atualizei minha postagem original. Segundo o link, o DirectX fica lento quando é necessário chamar GetFrontBufferData (). Há algo a considerar ao gravar as cenas do jogo? Você poderia contextualizar isso para mim?
someguy
1
O GDI é lento, portanto, não é adequado ao domínio do problema, DirectX ou OpenGL seria a única recomendação sensata.
6

Com o Windows 8, a Microsoft apresentou a API de duplicação da área de trabalho do Windows. Essa é a maneira oficialmente recomendada de fazê-lo. Um recurso interessante para a captura de tela é que ele detecta o movimento da janela, para que você possa transmitir deltas de bloco quando a janela é movida, em vez de pixels brutos. Além disso, informa quais retângulos foram alterados, de um quadro para o outro.

O código de exemplo da Microsoft é bastante complexo, mas a API é realmente simples e fácil de usar. Eu montei um projeto de exemplo que é muito mais simples que o exemplo oficial:

https://github.com/bmharper/WindowsDesktopDuplicationSample

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

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

Ben Harper
fonte
O projeto Github funciona perfeitamente no Windows 10, testado com vídeo na web e Resident Evil 7. A melhor coisa é a carga da CPU escalar com a taxa de atualização gráfica.
jw_ 17/04
Assim que você inicia o GPU-Z, esse programa para de capturar a tela.
Marino Šimić
5

Você pode experimentar o projeto de código aberto c ++ WinRobot @git , um poderoso capturador de tela

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;

Apoio, suporte :

  • Janela UAC
  • Winlogon
  • DirectShowOverlay
Caimão
fonte
1
Isso é ... muitos ganchos de baixo nível. A velocidade de captura é incrível se você tiver direitos de administrador.
Toster-cx
Achei o winrobot muito poderoso e suave.
Ashishgupta_mca 22/02
Estudei o código WinRobot e não vi nada de captura de tela wrt inovadora: ele está usando o mesmo CreateCompatibleDC..BitBlt. A menos que haja alguma mágica quando isso é realizado no contexto do serviço?
shekh 25/05
@shekh: Seu estudo foi muito superficial. O código usa IDirectDrawSurface7-> BltFast () para copiar a tela da superfície de desenho da tela para uma superfície DD de cópia e, em seguida, usa um Filemapping para copiar a imagem. É bastante complexo porque o código está sendo executado em um serviço em que você não pode acessar facilmente as áreas de trabalho.
Elmue 2/03
2

Eu mesmo faço isso com o directx e acho que é tão rápido quanto você gostaria que fosse. Eu não tenho um exemplo de código rápido, mas achei isso que deve ser útil. a versão do directx11 não deve diferir muito, o directx9 talvez um pouco mais, mas esse é o caminho a percorrer

cppanda
fonte
2

Sei que a sugestão a seguir não responde à sua pergunta, mas o método mais simples que encontrei para capturar uma visualização do DirectX que muda rapidamente é conectar uma câmera de vídeo na porta S-video da placa de vídeo e gravar as imagens como um filme. Depois transfira o vídeo da câmera de volta para um arquivo MPG, WMV, AVI etc. no computador.

Pierre
fonte
2

A gravação de tela pode ser feita em C # usando a API do VLC . Eu fiz um exemplo de programa para demonstrar isso. Ele usa as bibliotecas LibVLCSharp e VideoLAN.LibVLC.Windows . Você pode obter muitos outros recursos relacionados à renderização de vídeo usando esta API de plataforma cruzada.

Para documentação da 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
fonte
1
Para quem possa interessar: Observe que o libvlc é lançado sob a GPL. Se você não pretende liberar seu código na GPL, não use libvlc.
Sebastian Cabot
Isso não é exato, o LibVLC é LGPL - foi licenciado novamente em 2011. O aplicativo VLC em si continua sendo GPL: videolan.org/press/lgpl-libvlc.html
Andrew
0

Captura de mesa DXGI

Projeto que captura a imagem da área de trabalho com duplicação de DXGI. Salva a imagem capturada no arquivo em diferentes formatos de imagem (* .bmp; * .jpg; * .tif).

Este exemplo é escrito em C ++. Você também precisa de alguma experiência com o DirectX (D3D11, D2D1).

O que o aplicativo pode fazer

  • Se você tiver mais de um monitor de mesa, poderá escolher.
  • Redimensione a imagem da área de trabalho capturada.
  • Escolha diferentes modos de escala.
  • Você pode mostrar ou ocultar o ícone do mouse na imagem de saída.
  • Você pode girar a imagem para a imagem de saída ou deixá-la como padrão.
gerdogdu
fonte