3
\$\begingroup\$

I'm developing a screen recording application using java. I've modified code from https://dzone.com/articles/screen-record-play-using-java where all the external jars required for this application are available.

If I lower captureInterval to 25 then the application runs out of memory - i.e. it throws a java.lang.OutOfMemoryError exception on the line below within the Recorder method startRecord().

List<Image> resolutionVariants = mrImage.getResolutionVariants();

How can the code be more efficient with memory?

Recorder.java

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.MultiResolutionImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.imageio.ImageIO;
import javax.media.MediaLocator;


public class Recorder {
/**
 * Screen Width.
 */
public static final int screenWidth = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();

/**
 * Screen Height.
 */
public static final int screenHeight = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();

/**
 * Interval between which the image needs to be captured.
 */
public static final int captureInterval = 41;


/**
 * Base folder to store the recorded video.
 */
public static final String videoPath = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Records" + File.separator;

/**
 * Temporary folder to store the screenshot.
 */
public static final String store = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Records" + File.separator + "tmp" + File.separator;

/**
 * Status of the recorder.
 */
private static AtomicBoolean record = new AtomicBoolean(false);

private static Thread recordThread;
private static Thread processThread;

private static BlockingDeque<Image> imageList = new LinkedBlockingDeque<>();

public static void startRecord() {
    record.set(true);

    recordThread = new Thread(() -> {
        try {
            Robot rt = new Robot();
            Rectangle rect = new Rectangle(screenWidth, screenHeight);
            while (record.get()) {
                long startTime = System.currentTimeMillis();
                Image nativeResImage;
                MultiResolutionImage mrImage = rt.createMultiResolutionScreenCapture(rect);
                List<Image> resolutionVariants = mrImage.getResolutionVariants();
                if (resolutionVariants.size() > 1)
                    nativeResImage = resolutionVariants.get(1);
                else
                    nativeResImage = resolutionVariants.get(0);

                imageList.putLast(nativeResImage);

                long endTime = System.currentTimeMillis();
                long sleepTime = captureInterval - (endTime - startTime);

                if (sleepTime > 0)
                    Thread.sleep(sleepTime);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    });

    processThread = new Thread(() -> {
        while (record.get() || (!imageList.isEmpty())) {
            try {
                Image nativeResImage = imageList.takeFirst();
                ImageIO.write((BufferedImage) nativeResImage, "jpeg", new File(store
                        + System.currentTimeMillis() + ".jpeg"));
            } catch (InterruptedException | IOException e) {
                e.printStackTrace();
            }
        }
    });

    processThread.start();
    recordThread.start();
    System.out.println("\nEasy Capture is recording now!!!!!!!");
}
}

Main.java

import java.awt.*;
import java.io.File;
import java.util.Scanner;

import com.bel.recorder.Recorder;
public class Main {

    public static void main(String[] args) {
        System.out.println("######### Starting Easy Capture Recorder #######");
        Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
        System.out.println("Your Screen [Width, Height]: " + "[" + screen.getWidth() + ", " + screen.getHeight() + "]");
        Scanner sc = new Scanner(System.in);
        System.out.println("Rate [" + 24 + "] Frames/Per Sec.");

        File fileDir = new File(Recorder.videoPath);
        if (!fileDir.exists()) {
            if (!fileDir.mkdir())
                System.err.println("Error in creating the directory " + fileDir.getName());
        }

        fileDir = new File(Recorder.store);
        if (!fileDir.exists()) {
            if (!fileDir.mkdir())
                System.err.println("Error in creating the directory " + fileDir.getName());
        }

        Recorder.startRecord();

        System.out.println("Press x to exit:");
        String exit = sc.next();
        while (exit == null || "".equals(exit) || !"x".equalsIgnoreCase(exit)) {
            System.out.println("\nPress x to exit:");
            exit = sc.next();
        }

        // Recorder.stopRecord();

        File tmpDirectory = new File(Recorder.store);
        File[] files = tmpDirectory.listFiles();
        if (files == null)
            return;
        for (File file : files)
            file.delete();

        tmpDirectory.delete();
    }
}
\$\endgroup\$
8
  • \$\begingroup\$ Thanks for providing more information about the scenario. I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate. \$\endgroup\$ Jul 21 at 17:34
  • \$\begingroup\$ I bet it is not so much about memory efficiency, but the file IO throughput. It seems that recordThread starts to produce frames faster than processThread may save them to a disk. \$\endgroup\$
    – vnp
    Jul 21 at 21:34
  • \$\begingroup\$ @vnp can you tell a better way of writing images to disk. \$\endgroup\$
    – Harry
    Jul 22 at 2:27
  • \$\begingroup\$ As I said, you cannot write them faster than your HD can do. \$\endgroup\$
    – vnp
    Jul 22 at 4:58
  • \$\begingroup\$ As @vnp said and from the error it seems that your deque runs out all memory for the excessive number of the images present in the deque because you are created it with a capacity of Integer.Max_value. One thing I would do is create the deque with a fixed capacity with the appropriate constructor. \$\endgroup\$ Jul 22 at 8:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.