JavaFX:视频比正常速度快 2 倍

标签 java video javafx

我创建了一个屏幕录像机,通过拍摄大量屏幕截图来记录屏幕,然后将所有屏幕截图转换为 mov 文件。

但是,mov 文件的播放速度比正常速度快约 2 倍,我在多台计算机上尝试过,仍然出现相同的结果。

这是我的代码

主类

package recorder;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Recorder extends Application {

    public Stage stage;

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

        Scene scene = new Scene(root);
        stage.setResizable(false);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}

FXML文档

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="639.0" prefWidth="578.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="recorder.FXMLDocumentController">
    <columnConstraints>
        <ColumnConstraints hgrow="SOMETIMES" minWidth="578.0" prefWidth="578.0" />
    </columnConstraints>
    <rowConstraints>
        <RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" prefHeight="95.0" vgrow="SOMETIMES" />
        <RowConstraints maxHeight="245.0" minHeight="9.0" prefHeight="12.0" vgrow="SOMETIMES" />
        <RowConstraints maxHeight="481.0" minHeight="10.0" prefHeight="478.0" vgrow="SOMETIMES" />
    </rowConstraints>
    <children>
        <Button fx:id="start" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#startOnAction" prefHeight="220.0" prefWidth="578.0" text="Start Recording" />
        <TextArea fx:id="console" editable="false" prefHeight="200.0" prefWidth="200.0" promptText="Console" wrapText="true" GridPane.rowIndex="2" />
        <TextField fx:id="input" prefHeight="105.0" prefWidth="578.0" promptText="File Name" GridPane.rowIndex="1" />
    </children>
</GridPane>

FXML文档 Controller

package recorder;

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import javax.media.MediaLocator;
import javax.swing.JOptionPane;

public class FXMLDocumentController implements Initializable {

    ExecutorService imageSavingService = new ThreadPoolExecutor(2, 2,
            0L, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<Runnable>(20), new ThreadPoolExecutor.DiscardPolicy());

    boolean recording;

    File file;

    int num;
    /**
     * Screen Width.
     */
    public static int screenWidth = (int) Toolkit.getDefaultToolkit()
            .getScreenSize().getWidth();

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

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

    /**
     * Temporary folder to store the screenshot.
     */
    public static String store = "tmp";

    /**
     * Status of the recorder.
     */
    public static boolean record = false;

    @FXML
    private Button start;

    @FXML
    private TextArea console;

    @FXML
    private TextField input;

    public static void startRecord() {
        FXMLDocumentController t = new FXMLDocumentController();
//        Thread recordThread = new Thread() {
//            @Override
//            public void run() {
//                Robot rt;
//                int cnt = 0;
//                try {
//                    rt = new Robot();
//                    while (cnt == 0 || record) {
//                        BufferedImage img = rt.createScreenCapture(new Rectangle(screenWidth, screenHeight));
//                        ImageIO.write(img, "jpeg", new File("./" + store + "/"
//                                + System.currentTimeMillis() + ".jpeg"));
//                        if (cnt == 0) {
//                            record = true;
//                            cnt = 1;
//                        }
//                        t.wait(captureInterval);
//                    }
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            }
//        };
//        recordThread.start();
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    Robot rt;
                    int cnt = 0;
                    rt = new Robot();

                    BufferedImage img = rt.createScreenCapture(new Rectangle(screenWidth, screenHeight));
                    final long timeStemp = System.currentTimeMillis();
                    t.imageSavingService.submit(() -> {
                        try {
                            ImageIO.write(img, "jpeg", new File("./" + store + "/"
                                    + timeStemp + ".jpeg"));
                        } catch (IOException e) {
                            //handle exception (e.g. via callback)
                        }
                    });
                    if (cnt == 0) {
                        record = true;
                        cnt = 1;
                    }
                } catch (AWTException ex) {
                }
            }
        }, captureInterval, captureInterval);
    }

    public void makeVideo(String movFile) throws MalformedURLException {
        imageSavingService.shutdown();
        try {
            while (!imageSavingService.awaitTermination(5, TimeUnit.SECONDS)) {
                // waiting another 5 seconds for the service to terminate
            }
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        this.console.appendText("Processing Video... please wait");
        JpegImagesToMovie imageToMovie = new JpegImagesToMovie();
        Vector<String> imgLst = new Vector<String>();
        File f = new File(store);
        File[] fileLst = f.listFiles();
        for (int i = 0; i < fileLst.length; i++) {
            imgLst.add(fileLst[i].getAbsolutePath());
        }
        // Generate the output media locators.
        MediaLocator oml;
        if ((oml = imageToMovie.createMediaLocator(movFile)) == null) {
            this.console.appendText("Error in processing");
            System.exit(0);
        }
        imageToMovie.doIt(screenWidth, screenHeight, (1000 / captureInterval), imgLst, oml);
        f.deleteOnExit();
        System.exit(0);

    }

    @FXML
    void startOnAction(ActionEvent event) {
        recording = true;
        Timer timer = new Timer();
        this.console.setText("######### Starting Screen Recorder #########\n");
        Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
        this.console.appendText("Your Screen [Width, Height]: " + "[" + screen.getWidth() + "," + screen.getHeight() + "]\nWhen the Recording starts, this window will minimize, to stop them recording, open this window.");
        try {
            Thread.sleep(500);
        } catch (InterruptedException ex) {
        }
        this.console.appendText("The recording will start NOW\n");
        num = 5;

        File f = new File(store);
        if (!f.exists()) {
            f.mkdir();
        }
        startRecord();
        Stage stage = (Stage) console.getScene().getWindow();
        stage.setIconified(true);
        stage.focusedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
            focusState(newValue);
        });
    }

    private void focusState(boolean value) {
        if (recording) {
            try {
                makeVideo(input.getText() + ".mov");
                recording = false;
//        if (value) {
//            record = false;
//            try {
//                makeVideo(input.getText() + ".mov");
//            } catch (MalformedURLException ex) {
//            }
//        } else {
//        }
            } catch (MalformedURLException ex) {
            }
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {

    }

}

JpegImagesToMovie(不是我创建的)

package recorder;

/*
 * @(#)JpegImagesToMovie.java   1.3 01/03/13
 *
 * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

import java.awt.Dimension;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.util.Vector;

import javax.media.Buffer;
import javax.media.ConfigureCompleteEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.DataSink;
import javax.media.EndOfMediaEvent;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.PrefetchCompleteEvent;
import javax.media.Processor;
import javax.media.RealizeCompleteEvent;
import javax.media.ResourceUnavailableEvent;
import javax.media.Time;
import javax.media.control.TrackControl;
import javax.media.datasink.DataSinkErrorEvent;
import javax.media.datasink.DataSinkEvent;
import javax.media.datasink.DataSinkListener;
import javax.media.datasink.EndOfStreamEvent;
import javax.media.format.VideoFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.FileTypeDescriptor;
import javax.media.protocol.PullBufferDataSource;
import javax.media.protocol.PullBufferStream;

/**
 * This program takes a list of JPEG image files and convert them into a
 * QuickTime movie.
 */
public class JpegImagesToMovie implements ControllerListener, DataSinkListener {

    public boolean doIt(int width, int height, int frameRate, Vector inFiles,
            MediaLocator outML) throws MalformedURLException {
        ImageDataSource ids = new ImageDataSource(width, height, frameRate,
                inFiles);

        Processor p;

        try {
            //System.err
            //      .println("- create processor for the image datasource ...");
            p = Manager.createProcessor(ids);
        } catch (Exception e) {
            System.err
                    .println("Yikes!  Cannot create a processor from the data source.");
            return false;
        }

        p.addControllerListener(this);

        // Put the Processor into configured state so we can set
        // some processing options on the processor.
        p.configure();
        if (!waitForState(p, p.Configured)) {
            System.err.println("Failed to configure the processor.");
            return false;
        }

        // Set the output content descriptor to QuickTime.
        p.setContentDescriptor(new ContentDescriptor(
                FileTypeDescriptor.QUICKTIME));

        // Query for the processor for supported formats.
        // Then set it on the processor.
        TrackControl tcs[] = p.getTrackControls();
        Format f[] = tcs[0].getSupportedFormats();
        if (f == null || f.length <= 0) {
            System.err.println("The mux does not support the input format: "
                    + tcs[0].getFormat());
            return false;
        }

        tcs[0].setFormat(f[0]);

        //System.err.println("Setting the track format to: " + f[0]);

        // We are done with programming the processor. Let's just
        // realize it.
        p.realize();
        if (!waitForState(p, p.Realized)) {
            System.err.println("Failed to realize the processor.");
            return false;
        }

        // Now, we'll need to create a DataSink.
        DataSink dsink;
        if ((dsink = createDataSink(p, outML)) == null) {
            System.err
                    .println("Failed to create a DataSink for the given output MediaLocator: "
                            + outML);
            return false;
        }

        dsink.addDataSinkListener(this);
        fileDone = false;

        System.out.println("Generating the video : "+outML.getURL().toString());

        // OK, we can now start the actual transcoding.
        try {
            p.start();
            dsink.start();
        } catch (IOException e) {
            System.err.println("IO error during processing");
            return false;
        }

        // Wait for EndOfStream event.
        waitForFileDone();

        // Cleanup.
        try {
            dsink.close();
        } catch (Exception e) {
        }
        p.removeControllerListener(this);

        System.out.println("Video creation completed!!!!!");
        return true;
    }

    /**
     * Create the DataSink.
     */
    DataSink createDataSink(Processor p, MediaLocator outML) {

        DataSource ds;

        if ((ds = p.getDataOutput()) == null) {
            System.err
                    .println("Something is really wrong: the processor does not have an output DataSource");
            return null;
        }

        DataSink dsink;

        try {
            //System.err.println("- create DataSink for: " + outML);
            dsink = Manager.createDataSink(ds, outML);
            dsink.open();
        } catch (Exception e) {
            System.err.println("Cannot create the DataSink: " + e);
            return null;
        }

        return dsink;
    }

    Object waitSync = new Object();
    boolean stateTransitionOK = true;

    /**
     * Block until the processor has transitioned to the given state. Return
     * false if the transition failed.
     */
    boolean waitForState(Processor p, int state) {
        synchronized (waitSync) {
            try {
                while (p.getState() < state && stateTransitionOK)
                    waitSync.wait();
            } catch (Exception e) {
            }
        }
        return stateTransitionOK;
    }

    /**
     * Controller Listener.
     */
    public void controllerUpdate(ControllerEvent evt) {

        if (evt instanceof ConfigureCompleteEvent
                || evt instanceof RealizeCompleteEvent
                || evt instanceof PrefetchCompleteEvent) {
            synchronized (waitSync) {
                stateTransitionOK = true;
                waitSync.notifyAll();
            }
        } else if (evt instanceof ResourceUnavailableEvent) {
            synchronized (waitSync) {
                stateTransitionOK = false;
                waitSync.notifyAll();
            }
        } else if (evt instanceof EndOfMediaEvent) {
            evt.getSourceController().stop();
            evt.getSourceController().close();
        }
    }

    Object waitFileSync = new Object();
    boolean fileDone = false;
    boolean fileSuccess = true;

    /**
     * Block until file writing is done.
     */
    boolean waitForFileDone() {
        synchronized (waitFileSync) {
            try {
                while (!fileDone)
                    waitFileSync.wait();
            } catch (Exception e) {
            }
        }
        return fileSuccess;
    }

    /**
     * Event handler for the file writer.
     */
    public void dataSinkUpdate(DataSinkEvent evt) {

        if (evt instanceof EndOfStreamEvent) {
            synchronized (waitFileSync) {
                fileDone = true;
                waitFileSync.notifyAll();
            }
        } else if (evt instanceof DataSinkErrorEvent) {
            synchronized (waitFileSync) {
                fileDone = true;
                fileSuccess = false;
                waitFileSync.notifyAll();
            }
        }
    }

    /*public static void main(String args[]) {

        if (args.length == 0)
            prUsage();

        // Parse the arguments.
        int i = 0;
        int width = -1, height = -1, frameRate = 1;
        Vector inputFiles = new Vector();
        String outputURL = null;

        while (i < args.length) {

            if (args[i].equals("-w")) {
                i++;
                if (i >= args.length)
                    prUsage();
                width = new Integer(args[i]).intValue();
            } else if (args[i].equals("-h")) {
                i++;
                if (i >= args.length)
                    prUsage();
                height = new Integer(args[i]).intValue();
            } else if (args[i].equals("-f")) {
                i++;
                if (i >= args.length)
                    prUsage();
                frameRate = new Integer(args[i]).intValue();
            } else if (args[i].equals("-o")) {
                i++;
                if (i >= args.length)
                    prUsage();
                outputURL = args[i];
            } else {
                inputFiles.addElement(args[i]);
            }
            i++;
        }

        if (outputURL == null || inputFiles.size() == 0)
            prUsage();

        // Check for output file extension.
        if (!outputURL.endsWith(".mov") && !outputURL.endsWith(".MOV")) {
            System.err
                    .println("The output file extension should end with a .mov extension");
            prUsage();
        }

        if (width < 0 || height < 0) {
            System.err.println("Please specify the correct image size.");
            prUsage();
        }

        // Check the frame rate.
        if (frameRate < 1)
            frameRate = 1;

        // Generate the output media locators.
        MediaLocator oml;

        if ((oml = createMediaLocator(outputURL)) == null) {
            System.err.println("Cannot build media locator from: " + outputURL);
            System.exit(0);
        }

        JpegImagesToMovie imageToMovie = new JpegImagesToMovie();
        imageToMovie.doIt(width, height, frameRate, inputFiles, oml);

        System.exit(0);
    }*/

    static void prUsage() {
        System.err
                .println("Usage: java JpegImagesToMovie -w <width> -h <height> -f <frame rate> -o <output URL> <input JPEG file 1> <input JPEG file 2> ...");
        System.exit(-1);
    }

    /**
     * Create a media locator from the given string.
     */
    static MediaLocator createMediaLocator(String url) {

        MediaLocator ml;

        if (url.indexOf(":") > 0 && (ml = new MediaLocator(url)) != null)
            return ml;

        if (url.startsWith(File.separator)) {
            if ((ml = new MediaLocator("file:" + url)) != null)
                return ml;
        } else {
            String file = "file:" + System.getProperty("user.dir")
                    + File.separator + url;
            if ((ml = new MediaLocator(file)) != null)
                return ml;
        }

        return null;
    }

    // /////////////////////////////////////////////
    //
    // Inner classes.
    // /////////////////////////////////////////////

    /**
     * A DataSource to read from a list of JPEG image files and turn that into a
     * stream of JMF buffers. The DataSource is not seekable or positionable.
     */
    class ImageDataSource extends PullBufferDataSource {

        ImageSourceStream streams[];

        ImageDataSource(int width, int height, int frameRate, Vector images) {
            streams = new ImageSourceStream[1];
            streams[0] = new ImageSourceStream(width, height, frameRate, images);
        }

        public void setLocator(MediaLocator source) {
        }

        public MediaLocator getLocator() {
            return null;
        }

        /**
         * Content type is of RAW since we are sending buffers of video frames
         * without a container format.
         */
        public String getContentType() {
            return ContentDescriptor.RAW;
        }

        public void connect() {
        }

        public void disconnect() {
        }

        public void start() {
        }

        public void stop() {
        }

        /**
         * Return the ImageSourceStreams.
         */
        public PullBufferStream[] getStreams() {
            return streams;
        }

        /**
         * We could have derived the duration from the number of frames and
         * frame rate. But for the purpose of this program, it's not necessary.
         */
        public Time getDuration() {
            return DURATION_UNKNOWN;
        }

        public Object[] getControls() {
            return new Object[0];
        }

        public Object getControl(String type) {
            return null;
        }
    }

    /**
     * The source stream to go along with ImageDataSource.
     */
    class ImageSourceStream implements PullBufferStream {

        Vector images;
        int width, height;
        VideoFormat format;

        int nextImage = 0; // index of the next image to be read.
        boolean ended = false;

        public ImageSourceStream(int width, int height, int frameRate,
                Vector images) {
            this.width = width;
            this.height = height;
            this.images = images;

            format = new VideoFormat(VideoFormat.JPEG, new Dimension(width,
                    height), Format.NOT_SPECIFIED, Format.byteArray,
                    (float) frameRate);
        }

        /**
         * We should never need to block assuming data are read from files.
         */
        public boolean willReadBlock() {
            return false;
        }

        /**
         * This is called from the Processor to read a frame worth of video
         * data.
         */
        public void read(Buffer buf) throws IOException {

            // Check if we've finished all the frames.
            if (nextImage >= images.size()) {
                // We are done. Set EndOfMedia.
                //System.err.println("Done reading all images.");
                buf.setEOM(true);
                buf.setOffset(0);
                buf.setLength(0);
                ended = true;
                return;
            }

            String imageFile = (String) images.elementAt(nextImage);
            nextImage++;

            //System.err.println("  - reading image file: " + imageFile);

            // Open a random access file for the next image.
            RandomAccessFile raFile;
            raFile = new RandomAccessFile(imageFile, "r");

            byte data[] = null;

            // Check the input buffer type & size.

            if (buf.getData() instanceof byte[])
                data = (byte[]) buf.getData();

            // Check to see the given buffer is big enough for the frame.
            if (data == null || data.length < raFile.length()) {
                data = new byte[(int) raFile.length()];
                buf.setData(data);
            }

            // Read the entire JPEG image from the file.
            raFile.readFully(data, 0, (int) raFile.length());

            //System.err.println("    read " + raFile.length() + " bytes.");

            buf.setOffset(0);
            buf.setLength((int) raFile.length());
            buf.setFormat(format);
            buf.setFlags(buf.getFlags() | buf.FLAG_KEY_FRAME);

            // Close the random access file.
            raFile.close();
        }

        /**
         * Return the format of each video frame. That will be JPEG.
         */
        public Format getFormat() {
            return format;
        }

        public ContentDescriptor getContentDescriptor() {
            return new ContentDescriptor(ContentDescriptor.RAW);
        }

        public long getContentLength() {
            return 0;
        }

        public boolean endOfStream() {
            return ended;
        }

        public Object[] getControls() {
            return new Object[0];
        }

        public Object getControl(String type) {
            return null;
        }
    }
}

最佳答案

问题在于将图像写入磁盘的速度非常慢。这意味着在您的情况下,截取屏幕截图 + 将其写入磁盘大约需要 50 毫秒 -> 大约每 100 毫秒(50 毫秒捕获+保存,50 毫秒 sleep )保存一个屏幕截图 -> 生成的视频速度是原来的两倍。

可能的解决方案:

首先,您应该使用 java.util.Timer 来安排屏幕截图,而不是仅仅使用 Thread.sleep() ,因为它不考虑,任务需要多长时间。使用计时器,您可以以 50 毫秒的定期固定延迟来安排任务。

下一步是找到一种方法来更快地保存捕获的图像。 实现此目的的一种方法是将图像临时保存到线程安全队列中,并将“图像到磁盘”过程外包给与屏幕捕获过程并行运行的单独线程。理想情况下,这个单独的线程应该将图像直接编码为视频格式,但这需要您提供自己的 JpegImagesToMovie 版本(这意味着直接处理 Java 媒体框架)。

加快图像保存过程可以这样完成:

创建一个 ExecutorService 用于在录制开始时保存屏幕截图:

imageSavingService = new ThreadPoolExecutor(2, 2,
                0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(20), new ThreadPoolExecutor.DiscardPolicy());

这将创建一个池大小为 2 且工作队列容量为 20 的执行器。将工作队列限制为指定容量将防止超出内存。如果队列已满,新提交的帧将被丢弃(不应该发生)。通常单个线程应该是 IO 操作的最佳选择,但不知何故,当我测试它时,两个线程的性能要好得多。

您可以像这样向服务提交图像保存任务:

final long timeStemp = System.currentTimeMillis();
imageSavingService.submit(() -> {
    try {
        ImageIO.write(img, "jpeg", new File("./" + store + "/"
             + timeStemp + ".jpeg"));
    } catch (IOException e) {
        //handle exception (e.g. via callback)
    }
});  

取消录制后,您可以像这样关闭服务:

imageSavingService.shutdown();
try {
    while(!imageSavingService.awaitTermination(5, TimeUnit.SECONDS)){
        // waiting another 5 seconds for the service to terminate
    }
} catch (InterruptedException e1) { 
    e1.printStackTrace();
}

这样您就可以有序关闭服务并等待最后一个挂起的图像保存任务处理完毕。

使用此服务来加速图像保存以及建议的计时器,使我能够以 50 毫秒的捕获间隔进行记录而不会丢失帧。

关于JavaFX:视频比正常速度快 2 倍,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41413932/

相关文章:

java - 如何将excel文件的可见性设置为true

java - key 'PRIMARY' 的重复条目使用 JPA 持久保存到数据库中

android - ThumbnailUtils.createVideoThumbnail 在三星平板电脑 10.1 中不起作用

java - Android:AsyncTask 内的 Java Net Socket 异常

java - 选择要使用的根 DNS 服务器

android - 在 Android 中关闭奖励视频广告

php - WordPress 自动嵌入 YouTube 视频 - 添加过滤器以处理 `end` 属性

javafx - 传递参数JavaFX FXML

java - FXML ListView,我无法将 ObservableArrayList<Pane> 添加到其中

javafx - fxml:如何处理 Controller 中的按钮操作?