Como tirar rapidamente uma canvas da área de trabalho com Java no Windows (ffmpeg, etc.)?

Eu gostaria de usar o java para tirar um screenshot da minha máquina usando o FFMPEG ou alguma outra solução. Eu sei linux funciona com ffmpeg sem JNI, mas executá-lo no Windows não funciona e pode exigir (JNI?) Existe alguma amostra de alguma class Java simples (e qualquer outra coisa necessária) para capturar uma imagem executável em um ambiente Windows? Existe alguma alternativa ao FFMPEG? Eu quero fazer uma captura de canvas em uma taxa mais rápida do que a API Java Robot, que eu encontrei para trabalhar em tirar screenshots, mas é mais lento do que eu gostaria.

Eu sei no Linux isso funciona muito rápido:

import com.googlecode.javacv.*; public class ScreenGrabber { public static void main(String[] args) throws Exception { int x = 0, y = 0, w = 1024, h = 768; FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(":0.0+" + x + "," + y); grabber.setFormat("x11grab"); grabber.setImageWidth(w); grabber.setImageHeight(h); grabber.start(); CanvasFrame frame = new CanvasFrame("Screen Capture"); while (frame.isVisible()) { frame.showImage(grabber.grab()); } frame.dispose(); grabber.stop(); } 

Isso não funciona no ambiente do Windows. Não tenho certeza se há alguma maneira que eu poderia usar esse mesmo código, mas use javacpp para realmente fazê-lo funcionar sem ter que mudar muito do código acima.

O objective é tirar screenshots da canvas rapidamente, mas depois parar depois de tirar uma screenshot que é “diferente”, aka. canvas alterada por causa de algum evento como, uma janela é fechada, etc.

Usar a class Robots integrada é muito mais fácil do que outras bibliotecas Java e provavelmente deve atender às suas necessidades.

Se você precisar de um vídeo suave com> = 30fps (mais de 30 capturas de canvas por segundo), primeiro tente a abordagem de Robôs, além de melhorar o desempenho usando o armazenamento asynchronous das capturas de canvas .

Se não funcionar para você, tente usar o JNA e isso é (mesmo que seja mais complexo), quase garantido que funcione para uma captura de canvas suave.

Abordagem com robôs

A class de robôs é realmente capaz de fazer o que você quer, o problema que a maioria das abordagens de captura de canvas com Robots tem é o salvamento das capturas de canvas. Uma abordagem poderia parecer assim: Fazer um loop sobre o método captureScreen (), pegar a canvas em um BufferedImage, convertê-lo em uma matriz de bytes e salvá-lo com um gravador de arquivo asynchronous em um arquivo de destino depois de adicionar a referência futura de sua imagem ArrayList para poder continuar enquanto armazena os dados da imagem.

 // Pseudo code while (capturing) { grab bufferedImage (screenCapture) from screen convert bufferImage to byte array start asynchronous file channel to write to the output file and add the future reference (return value) to the ArrayList } 

Abordagem com o JNA

Pergunta Original: Como tirar screenshots rapidamente em Java?

Como é uma má prática apenas linkar, vou postar o exemplo aqui:

 import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferUShort; import java.awt.image.DirectColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import com.sun.jna.Native; import com.sun.jna.platform.win32.W32API; import com.sun.jna.win32.W32APIOptions; public class JNAScreenShot { public static BufferedImage getScreenshot(Rectangle bounds) { W32API.HDC windowDC = GDI.GetDC(USER.GetDesktopWindow()); W32API.HBITMAP outputBitmap = GDI.CreateCompatibleBitmap(windowDC, bounds.width, bounds.height); try { W32API.HDC blitDC = GDI.CreateCompatibleDC(windowDC); try { W32API.HANDLE oldBitmap = GDI.SelectObject(blitDC, outputBitmap); try { GDI.BitBlt(blitDC, 0, 0, bounds.width, bounds.height, windowDC, bounds.x, bounds.y, GDI32.SRCCOPY); } finally { GDI.SelectObject(blitDC, oldBitmap); } GDI32.BITMAPINFO bi = new GDI32.BITMAPINFO(40); bi.bmiHeader.biSize = 40; boolean ok = GDI.GetDIBits(blitDC, outputBitmap, 0, bounds.height, (byte[]) null, bi, GDI32.DIB_RGB_COLORS); if (ok) { GDI32.BITMAPINFOHEADER bih = bi.bmiHeader; bih.biHeight = -Math.abs(bih.biHeight); bi.bmiHeader.biCompression = 0; return bufferedImageFromBitmap(blitDC, outputBitmap, bi); } else { return null; } } finally { GDI.DeleteObject(blitDC); } } finally { GDI.DeleteObject(outputBitmap); } } private static BufferedImage bufferedImageFromBitmap(GDI32.HDC blitDC, GDI32.HBITMAP outputBitmap, GDI32.BITMAPINFO bi) { GDI32.BITMAPINFOHEADER bih = bi.bmiHeader; int height = Math.abs(bih.biHeight); final ColorModel cm; final DataBuffer buffer; final WritableRaster raster; int strideBits = (bih.biWidth * bih.biBitCount); int strideBytesAligned = (((strideBits - 1) | 0x1F) + 1) >> 3; final int strideElementsAligned; switch (bih.biBitCount) { case 16: strideElementsAligned = strideBytesAligned / 2; cm = new DirectColorModel(16, 0x7C00, 0x3E0, 0x1F); buffer = new DataBufferUShort(strideElementsAligned * height); raster = Raster.createPackedRaster(buffer, bih.biWidth, height, strideElementsAligned, ((DirectColorModel) cm).getMasks(), null); break; case 32: strideElementsAligned = strideBytesAligned / 4; cm = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF); buffer = new DataBufferInt(strideElementsAligned * height); raster = Raster.createPackedRaster(buffer, bih.biWidth, height, strideElementsAligned, ((DirectColorModel) cm).getMasks(), null); break; default: throw new IllegalArgumentException("Unsupported bit count: " + bih.biBitCount); } final boolean ok; switch (buffer.getDataType()) { case DataBuffer.TYPE_INT: { int[] pixels = ((DataBufferInt) buffer).getData(); ok = GDI.GetDIBits(blitDC, outputBitmap, 0, raster.getHeight(), pixels, bi, 0); } break; case DataBuffer.TYPE_USHORT: { short[] pixels = ((DataBufferUShort) buffer).getData(); ok = GDI.GetDIBits(blitDC, outputBitmap, 0, raster.getHeight(), pixels, bi, 0); } break; default: throw new AssertionError("Unexpected buffer element type: " + buffer.getDataType()); } if (ok) { return new BufferedImage(cm, raster, false, null); } else { return null; } } private static final User32 USER = User32.INSTANCE; private static final GDI32 GDI = GDI32.INSTANCE; } interface GDI32 extends com.sun.jna.platform.win32.GDI32 { GDI32 INSTANCE = (GDI32) Native.loadLibrary(GDI32.class); boolean BitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, int dwRop); HDC GetDC(HWND hWnd); boolean GetDIBits(HDC dc, HBITMAP bmp, int startScan, int scanLines, byte[] pixels, BITMAPINFO bi, int usage); boolean GetDIBits(HDC dc, HBITMAP bmp, int startScan, int scanLines, short[] pixels, BITMAPINFO bi, int usage); boolean GetDIBits(HDC dc, HBITMAP bmp, int startScan, int scanLines, int[] pixels, BITMAPINFO bi, int usage); int SRCCOPY = 0xCC0020; } interface User32 extends com.sun.jna.platform.win32.User32 { User32 INSTANCE = (User32) Native.loadLibrary(User32.class, W32APIOptions.UNICODE_OPTIONS); HWND GetDesktopWindow(); } 

Mais informações e abordagens

Veja também

Você precisará usar JNI ou JNA para chamar uma combinação de CreateCompatibleBitmap, XGetImage, DirectX ou OpenGL para capturar uma captura de canvas e, em seguida, copiar alguns dados brutos de bitmap de volta para Java. Meu perfil mostrou uma aceleração de cerca de 400% em relação à class Robot ao acessar dados brutos de bitmap no X11. Eu não testei outras plataformas neste momento. Algum código muito antigo está disponível aqui, mas eu não tive muito tempo para trabalhar nele recentemente.

De acordo com a documentação oficial do ffmpeg você deve ser capaz de manter uma plataforma cruzada se você fizer o parâmetro file passado para o FFmpegFrameGrabber (que é realmente um parâmetro de input que é passado como a opção -i para o ffmpeg) aderir aos diferentes formatos cada device espera .

ie:

para Windows: dshow espera -i video="screen-capture-recorder"

para OSX: avfoundation espera -i "":

e para o Linux: x11grab espera -i :+, .

Portanto, apenas passar esses valores (argumentos para -i ) para o construtor e configurar o formato (via setFormat ) deve fazer o truque.

Você está familiarizado com o Xuggler? Ele usa o FFmpeg para codificação e decodificação. Fiquei sabendo há alguns meses, quando precisei extrair frameworks de um vídeo e funcionou sem problemas.

No site oficial você pode encontrar alguns exemplos, incluindo um chamado “CaptureScreenToFile.java”. Para mais informações, siga estes links:

http://www.xuggle.com/xuggler/

https://github.com/artclarke/xuggle-xuggler/tree/master/src/com/xuggle/xuggler/demos