/*******************************************************************************
* Copyright (c) 2008, 2010 Xuggle Inc. All rights reserved.
*
* This file is part of Xuggle-Xuggler-Main.
*
* Xuggle-Xuggler-Main is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Xuggle-Xuggler-Main is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Xuggle-Xuggler-Main. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package com.xuggle.xuggler.demos;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IPacket;
import com.xuggle.xuggler.IPixelFormat;
import com.xuggle.xuggler.IRational;
import com.xuggle.xuggler.IStream;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.IVideoPicture;
import com.xuggle.xuggler.video.ConverterFactory;
import com.xuggle.xuggler.video.IConverter;
/**
* This demonstration application shows how to use Xuggler and Java to take
* snapshots of your screen and encode them in a video.
*
* <p>
* You can find the original code <a href="http://flexingineer.blogspot.com/2009/05/encoding-video-from-series-of-images.html"
* > here </a>.
* </p>
*
* @author Denis Zgonjanin
* @author aclarke
*
*/
public class CaptureScreenToFile
{
private final IContainer outContainer;
private final IStream outStream;
private final IStreamCoder outStreamCoder;
private final IRational frameRate;
private final Robot robot;
private final Toolkit toolkit;
private final Rectangle screenBounds;
private long firstTimeStamp=-1;
/**
* Takes up to one argument, which just gives a file name to encode as. We
* guess which video codec to use based on the filename.
*
* @param args
* The filename to write to.
*/
public static void main(String[] args)
{
String outFile = null;
if (args.length < 1)
{
outFile = "output.mp4";
}
else if (args.length == 1)
{
outFile = args[0];
}
else if (args.length > 1)
{
System.err.println("Must pass in an output file name");
return;
}
try
{
CaptureScreenToFile videoEncoder = new CaptureScreenToFile(outFile);
int index = 0;
while (index < 15*videoEncoder.frameRate.getDouble())
{
System.out.println("encoded image");
videoEncoder.encodeImage(videoEncoder.takeSingleSnapshot());
try
{
// sleep for framerate milliseconds
Thread.sleep((long) (1000 / videoEncoder.frameRate.getDouble()));
}
catch (InterruptedException e)
{
e.printStackTrace(System.out);
}
index++;
}
videoEncoder.closeStreams();
}
catch (RuntimeException e)
{
System.err.println("we can't get permission to capture the screen");
}
}
/**
* Create the demonstration object with lots of defaults. Throws an exception
* if we can't get the screen or open the file.
*
* @param outFile
* File to write to.
*/
public CaptureScreenToFile(String outFile)
{
try
{
robot = new Robot();
}
catch (AWTException e)
{
System.out.println(e.getMessage());
throw new RuntimeException(e);
}
toolkit = Toolkit.getDefaultToolkit();
screenBounds = new Rectangle(toolkit.getScreenSize());
// Change this to change the frame rate you record at
frameRate = IRational.make(3, 1);
outContainer = IContainer.make();
int retval = outContainer.open(outFile, IContainer.Type.WRITE, null);
if (retval < 0)
throw new RuntimeException("could not open output file");
ICodec codec = ICodec.guessEncodingCodec(null, null, outFile, null,
ICodec.Type.CODEC_TYPE_VIDEO);
if (codec == null)
throw new RuntimeException("could not guess a codec");
outStream = outContainer.addNewStream(codec);
outStreamCoder = outStream.getStreamCoder();
outStreamCoder.setNumPicturesInGroupOfPictures(30);
outStreamCoder.setCodec(codec);
outStreamCoder.setBitRate(25000);
outStreamCoder.setBitRateTolerance(9000);
int width = toolkit.getScreenSize().width;
int height = toolkit.getScreenSize().height;
outStreamCoder.setPixelType(IPixelFormat.Type.YUV420P);
outStreamCoder.setHeight(height);
outStreamCoder.setWidth(width);
outStreamCoder.setFlag(IStreamCoder.Flags.FLAG_QSCALE, true);
outStreamCoder.setGlobalQuality(0);
outStreamCoder.setFrameRate(frameRate);
outStreamCoder.setTimeBase(IRational.make(frameRate.getDenominator(),
frameRate.getNumerator()));
retval = outStreamCoder.open(null, null);
if (retval < 0)
throw new RuntimeException("could not open input decoder");
retval = outContainer.writeHeader();
if (retval < 0)
throw new RuntimeException("could not write file header");
}
/**
* Encode the given image to the file and increment our time stamp.
*
* @param originalImage
* an image of the screen.
*/
public void encodeImage(BufferedImage originalImage)
{
BufferedImage worksWithXugglerBufferedImage = convertToType(originalImage,
BufferedImage.TYPE_3BYTE_BGR);
IPacket packet = IPacket.make();
long now = System.currentTimeMillis();
if (firstTimeStamp == -1)
firstTimeStamp = now;
IConverter converter = null;
try
{
converter = ConverterFactory.createConverter(
worksWithXugglerBufferedImage, IPixelFormat.Type.YUV420P);
}
catch (UnsupportedOperationException e)
{
System.out.println(e.getMessage());
e.printStackTrace(System.out);
}
long timeStamp = (now - firstTimeStamp)*1000; // convert to microseconds
IVideoPicture outFrame = converter.toPicture(worksWithXugglerBufferedImage,
timeStamp);
outFrame.setQuality(0);
int retval = outStreamCoder.encodeVideo(packet, outFrame, 0);
if (retval < 0)
throw new RuntimeException("could not encode video");
if (packet.isComplete())
{
retval = outContainer.writePacket(packet);
if (retval < 0)
throw new RuntimeException("could not save packet to container");
}
}
/**
* Close out the file we're currently working on.
*/
public void closeStreams()
{
int retval = outContainer.writeTrailer();
if (retval < 0)
throw new RuntimeException("Could not write trailer to output file");
}
/**
* Use the AWT robot to take a snapshot of the current screen.
*
* @return a picture of the desktop
*/
public BufferedImage takeSingleSnapshot()
{
return robot.createScreenCapture(this.screenBounds);
}
/**
* Convert a {@link BufferedImage} of any type, to {@link BufferedImage} of a
* specified type. If the source image is the same type as the target type,
* then original image is returned, otherwise new image of the correct type is
* created and the content of the source image is copied into the new image.
*
* @param sourceImage
* the image to be converted
* @param targetType
* the desired BufferedImage type
*
* @return a BufferedImage of the specifed target type.
*
* @see BufferedImage
*/
public static BufferedImage convertToType(BufferedImage sourceImage,
int targetType)
{
BufferedImage image;
// if the source image is already the target type, return the source image
if (sourceImage.getType() == targetType)
image = sourceImage;
// otherwise create a new image of the target type and draw the new
// image
else
{
image = new BufferedImage(sourceImage.getWidth(),
sourceImage.getHeight(), targetType);
image.getGraphics().drawImage(sourceImage, 0, 0, null);
}
return image;
}
}