001/******************************************************************************* 002 * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved. 003 * 004 * This file is part of AVPKit. 005 * 006 * AVPKit is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU Lesser General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * AVPKit is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with AVPKit. If not, see <http://www.gnu.org/licenses/>. 018 *******************************************************************************/ 019 020package com.avpkit.core.demos; 021import java.awt.AWTException; 022import java.awt.Rectangle; 023import java.awt.Robot; 024import java.awt.Toolkit; 025import java.awt.image.BufferedImage; 026 027import com.avpkit.core.ICodec; 028import com.avpkit.core.IContainer; 029import com.avpkit.core.IPacket; 030import com.avpkit.core.IPixelFormat; 031import com.avpkit.core.IRational; 032import com.avpkit.core.IStream; 033import com.avpkit.core.IStreamCoder; 034import com.avpkit.core.IVideoPicture; 035import com.avpkit.core.video.ConverterFactory; 036import com.avpkit.core.video.IConverter; 037 038/** 039 * This demonstration application shows how to use AVPKit and Java to take 040 * snapshots of your screen and encode them in a video. 041 * 042 * <p> 043 * You can find the original code <a href="http://flexingineer.blogspot.com/2009/05/encoding-video-from-series-of-images.html" 044 * > here </a>. 045 * </p> 046 * 047 * @author Denis Zgonjanin 048 * @author aclarke 049 * 050 */ 051 052public class CaptureScreenToFile 053{ 054 private final IContainer outContainer; 055 private final IStream outStream; 056 private final IStreamCoder outStreamCoder; 057 058 private final IRational frameRate; 059 060 private final Robot robot; 061 private final Toolkit toolkit; 062 private final Rectangle screenBounds; 063 064 private long firstTimeStamp=-1; 065 066 /** 067 * Takes up to one argument, which just gives a file name to encode as. We 068 * guess which video codec to use based on the filename. 069 * 070 * @param args 071 * The filename to write to. 072 */ 073 074 public static void main(String[] args) 075 { 076 String outFile = null; 077 if (args.length < 1) 078 { 079 outFile = "output.mp4"; 080 } 081 else if (args.length == 1) 082 { 083 outFile = args[0]; 084 } 085 else if (args.length > 1) 086 { 087 System.err.println("Must pass in an output file name"); 088 return; 089 } 090 try 091 { 092 CaptureScreenToFile videoEncoder = new CaptureScreenToFile(outFile); 093 int index = 0; 094 while (index < 15*videoEncoder.frameRate.getDouble()) 095 { 096 System.out.println("encoded image"); 097 videoEncoder.encodeImage(videoEncoder.takeSingleSnapshot()); 098 099 try 100 { 101 // sleep for framerate milliseconds 102 Thread.sleep((long) (1000 / videoEncoder.frameRate.getDouble())); 103 } 104 catch (InterruptedException e) 105 { 106 e.printStackTrace(System.out); 107 } 108 index++; 109 } 110 videoEncoder.closeStreams(); 111 } 112 catch (RuntimeException e) 113 { 114 System.err.println("we can't get permission to capture the screen"); 115 } 116 } 117 118 /** 119 * Create the demonstration object with lots of defaults. Throws an exception 120 * if we can't get the screen or open the file. 121 * 122 * @param outFile 123 * File to write to. 124 */ 125 public CaptureScreenToFile(String outFile) 126 { 127 try 128 { 129 robot = new Robot(); 130 } 131 catch (AWTException e) 132 { 133 System.out.println(e.getMessage()); 134 throw new RuntimeException(e); 135 } 136 toolkit = Toolkit.getDefaultToolkit(); 137 screenBounds = new Rectangle(toolkit.getScreenSize()); 138 139 // Change this to change the frame rate you record at 140 frameRate = IRational.make(3, 1); 141 142 outContainer = IContainer.make(); 143 144 int retval = outContainer.open(outFile, IContainer.Type.WRITE, null); 145 if (retval < 0) 146 throw new RuntimeException("could not open output file"); 147 148 ICodec codec = ICodec.guessEncodingCodec(null, null, outFile, null, 149 ICodec.Type.CODEC_TYPE_VIDEO); 150 if (codec == null) 151 throw new RuntimeException("could not guess a codec"); 152 153 outStream = outContainer.addNewStream(codec); 154 outStreamCoder = outStream.getStreamCoder(); 155 156 outStreamCoder.setNumPicturesInGroupOfPictures(30); 157 outStreamCoder.setCodec(codec); 158 159 outStreamCoder.setBitRate(25000); 160 outStreamCoder.setBitRateTolerance(9000); 161 162 int width = toolkit.getScreenSize().width; 163 int height = toolkit.getScreenSize().height; 164 165 outStreamCoder.setPixelType(IPixelFormat.Type.YUV420P); 166 outStreamCoder.setHeight(height); 167 outStreamCoder.setWidth(width); 168 outStreamCoder.setFlag(IStreamCoder.Flags.FLAG_QSCALE, true); 169 outStreamCoder.setGlobalQuality(0); 170 171 outStreamCoder.setFrameRate(frameRate); 172 outStreamCoder.setTimeBase(IRational.make(frameRate.getDenominator(), 173 frameRate.getNumerator())); 174 175 retval = outStreamCoder.open(null, null); 176 if (retval < 0) 177 throw new RuntimeException("could not open input decoder"); 178 retval = outContainer.writeHeader(); 179 if (retval < 0) 180 throw new RuntimeException("could not write file header"); 181 } 182 183 /** 184 * Encode the given image to the file and increment our time stamp. 185 * 186 * @param originalImage 187 * an image of the screen. 188 */ 189 190 public void encodeImage(BufferedImage originalImage) 191 { 192 BufferedImage worksWithAVPKitBufferedImage = convertToType(originalImage, 193 BufferedImage.TYPE_3BYTE_BGR); 194 IPacket packet = IPacket.make(); 195 196 long now = System.currentTimeMillis(); 197 if (firstTimeStamp == -1) 198 firstTimeStamp = now; 199 200 IConverter converter = null; 201 try 202 { 203 converter = ConverterFactory.createConverter( 204 worksWithAVPKitBufferedImage, IPixelFormat.Type.YUV420P); 205 } 206 catch (UnsupportedOperationException e) 207 { 208 System.out.println(e.getMessage()); 209 e.printStackTrace(System.out); 210 } 211 212 long timeStamp = (now - firstTimeStamp)*1000; // convert to microseconds 213 IVideoPicture outFrame = converter.toPicture(worksWithAVPKitBufferedImage, 214 timeStamp); 215 216 outFrame.setQuality(0); 217 int retval = outStreamCoder.encodeVideo(packet, outFrame, 0); 218 if (retval < 0) 219 throw new RuntimeException("could not encode video"); 220 if (packet.isComplete()) 221 { 222 retval = outContainer.writePacket(packet); 223 if (retval < 0) 224 throw new RuntimeException("could not save packet to container"); 225 } 226 227 } 228 229 /** 230 * Close out the file we're currently working on. 231 */ 232 233 public void closeStreams() 234 { 235 int retval = outContainer.writeTrailer(); 236 if (retval < 0) 237 throw new RuntimeException("Could not write trailer to output file"); 238 } 239 240 /** 241 * Use the AWT robot to take a snapshot of the current screen. 242 * 243 * @return a picture of the desktop 244 */ 245 246 public BufferedImage takeSingleSnapshot() 247 { 248 return robot.createScreenCapture(this.screenBounds); 249 } 250 251 /** 252 * Convert a {@link BufferedImage} of any type, to {@link BufferedImage} of a 253 * specified type. If the source image is the same type as the target type, 254 * then original image is returned, otherwise new image of the correct type is 255 * created and the content of the source image is copied into the new image. 256 * 257 * @param sourceImage 258 * the image to be converted 259 * @param targetType 260 * the desired BufferedImage type 261 * 262 * @return a BufferedImage of the specifed target type. 263 * 264 * @see BufferedImage 265 */ 266 267 public static BufferedImage convertToType(BufferedImage sourceImage, 268 int targetType) 269 { 270 BufferedImage image; 271 272 // if the source image is already the target type, return the source image 273 274 if (sourceImage.getType() == targetType) 275 image = sourceImage; 276 277 // otherwise create a new image of the target type and draw the new 278 // image 279 280 else 281 { 282 image = new BufferedImage(sourceImage.getWidth(), 283 sourceImage.getHeight(), targetType); 284 image.getGraphics().drawImage(sourceImage, 0, 0, null); 285 } 286 287 return image; 288 } 289 290}