【问题标题】:High refreshing rate in JavaFXJavaFX中的高刷新率
【发布时间】:2019-07-22 07:52:24
【问题描述】:

我正在尝试编写一个带有均衡器、频率分析仪和声级计的程序。模型部分似乎工作得很好,但我正在用 IHM 试验一些错误。

我的最后一个错误是水平仪。一段时间后(从几毫秒到几秒),它会冻结并且不再更新。因此,这是它的(简化)版本。我添加了可运行部分来测试和重现错误。当然,当我添加其他也需要频繁刷新的图形组件时,这个错误会更快出现。例如,频率分析由大约 1000 个点的折线图表示。

public class LevelMeter2 extends Parent implements Runnable {

    private IntegerProperty levelMeterHeight = new SimpleIntegerProperty();

    private Rectangle led;

    private IntegerProperty height = new SimpleIntegerProperty();
    private IntegerProperty width = new SimpleIntegerProperty();
    private DoubleProperty linearValue = new SimpleDoubleProperty();
    private Color backgroundColor=Color.BLACK;
    private double minLinearValue, maxLinearValue;

    public LevelMeter2 (int height2, int width2) {
        this.height.set(height2);
        this.levelMeterHeight.bind(height.multiply(0.9));
        this.width.set(width2);

        linearValue.set(1.0);
        minLinearValue = Math.pow(10, -60.0/100);
        maxLinearValue = Math.pow(10, 3.0/100)-minLinearValue;

        Rectangle levelMeterShape = new Rectangle();
        levelMeterShape.widthProperty().bind(width);
        levelMeterShape.heightProperty().bind(height);
        levelMeterShape.setStroke(backgroundColor);

        this.getChildren().add(levelMeterShape);

        led = new Rectangle();
        led.widthProperty().bind(width.multiply(0.8));
        led.translateXProperty().bind(width.multiply(0.1));
        led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
        led.setFill(Color.AQUA);
        Rotate rotate = new Rotate();
        rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
        rotate.pivotYProperty().bind(height.divide(2));
        rotate.setAngle(180);
        led.getTransforms().add(rotate);
        this.getChildren().add(led);
        }

    public double convertdBToLinearValue (double dB) {
        return ((double)Math.round(100 * ((Math.pow(10, dB/100)-minLinearValue)/maxLinearValue)) ) /100   ;
        //return (Math.pow(10, dB/100)-minLinearValue)/maxLinearValue;
    }

    public double convertLinearValueTodB (double linearValue) {
        return 100*Math.log10(linearValue*maxLinearValue+minLinearValue);
    }

    public void setValue (double dB) {
        if (dB>3) {
            dB=3;
        }   
        linearValue.setValue(convertdBToLinearValue(dB));
    }

    @Override
    public void run() {
        int i = 0;
        double value=-20;
        while (i<1000) {
            setValue(value);
            value = (Math.random()-0.5)*10+value;
            if (value>3) {
                value=3;
            }
            if (value<-60) {
                value=-60;
            }
            i++;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("END OF WHILE");
    }
}

还有一个“主要”来测试它:

public class MainGraph extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        HBox pane = new HBox();
        LevelMeter2 levelMeter = new LevelMeter2(300,30);
        Thread t = new Thread(levelMeter);
        pane.getChildren().add(levelMeter);

        t.start();
        Scene scene = new Scene(pane, 300, 300);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Test IHM");
        primaryStage.setOnCloseRequest( event -> {
            System.out.println("FIN");
            System.exit(0);     
        });
        primaryStage.show();
    }    
}

我的代码有什么问题?如何编写更强大的代码,让我的 IHM 具有高刷新率?或者我怎样才能防止冻结?

感谢您的帮助。

【问题讨论】:

    标签: javafx refresh frame-rate


    【解决方案1】:

    我建议你离开 Threads 并使用 JavaFX Animation 包中的东西。在此示例中使用 Timeline。此代码设置为以大约 60 fps 的速率运行。您可以使用Duration.millis() 进行调整。

    >Main
    
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    /**
     *
     * @author blj0011
     */
    public class JavaFXApplication342 extends Application
    {
    
        @Override
        public void start(Stage primaryStage)
        {
    
            LevelMeter2 levelMeter = new LevelMeter2(300, 30);
    
            Button button = new Button("Start");
            button.setOnAction((event) -> {
                switch (button.getText()) {
                    case "Start":
                        levelMeter.startAnimation();
                        button.setText("Stop");
                        break;
                    case "Stop":
                        levelMeter.stopAnimation();
                        button.setText("Start");
                        break;
                }
            });
    
            HBox pane = new HBox(levelMeter, button);
            Scene scene = new Scene(pane, 300, 300);
            primaryStage.setScene(scene);
            primaryStage.setTitle("Test IHM");
            primaryStage.setOnCloseRequest(event -> {
                System.out.println("FIN");
                System.exit(0);
            });
            primaryStage.show();
        }
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args)
        {
            launch(args);
        }
    
    }
    

    LevelMeter2

    import javafx.animation.KeyFrame;
    import javafx.animation.Timeline;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.scene.Parent;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.transform.Rotate;
    import javafx.util.Duration;
    
    public final class LevelMeter2 extends Parent
    {
    
        private final IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
    
        Timeline timeline;
        double value = -20;
    
        private final Rectangle led;
    
        private final IntegerProperty height = new SimpleIntegerProperty();
        private final IntegerProperty width = new SimpleIntegerProperty();
        private final DoubleProperty linearValue = new SimpleDoubleProperty();
        private final Color backgroundColor = Color.BLACK;
        private final double minLinearValue;
        private final double maxLinearValue;
    
        public LevelMeter2(int height2, int width2)
        {
            this.height.set(height2);
            this.levelMeterHeight.bind(height.multiply(0.9));
            this.width.set(width2);
    
            linearValue.set(1.0);
            minLinearValue = Math.pow(10, -60.0 / 100);
            maxLinearValue = Math.pow(10, 3.0 / 100) - minLinearValue;
    
            Rectangle levelMeterShape = new Rectangle();
            levelMeterShape.widthProperty().bind(width);
            levelMeterShape.heightProperty().bind(height);
            levelMeterShape.setStroke(backgroundColor);
    
            this.getChildren().add(levelMeterShape);
    
            led = new Rectangle();
            led.widthProperty().bind(width.multiply(0.8));
            led.translateXProperty().bind(width.multiply(0.1));
            led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
            led.setFill(Color.AQUA);
            Rotate rotate = new Rotate();
            rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
            rotate.pivotYProperty().bind(height.divide(2));
            rotate.setAngle(180);
            led.getTransforms().add(rotate);
            getChildren().add(led);
    
            timeline = new Timeline(new KeyFrame(Duration.millis(16), (event) -> {
                setValue(value);
    
                value = (Math.random() - 0.5) * 10 + value;
                if (value > 3) {
                    value = 3;
                }
                if (value < -60) {
                    value = -60;
                }
    
            }));
            timeline.setCycleCount(Timeline.INDEFINITE);
        }
    
        public double convertdBToLinearValue(double dB)
        {
            return ((double) Math.round(100 * ((Math.pow(10, dB / 100) - minLinearValue) / maxLinearValue))) / 100;
        }
    
        public double convertLinearValueTodB(double linearValue)
        {
            return 100 * Math.log10(linearValue * maxLinearValue + minLinearValue);
        }
    
        public void setValue(double dB)
        {
            if (dB > 3) {
                dB = 3;
            }
            linearValue.setValue(convertdBToLinearValue(dB));
        }
    
        public void startAnimation()
        {
            timeline.play();
        }
    
        public void stopAnimation()
        {
            timeline.stop();
        }
    }
    

    多个 LevelMeters 示例:

    主要

    import java.util.ArrayList;
    import java.util.List;
    import javafx.animation.ParallelTransition;
    import javafx.animation.Timeline;
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.StackPane;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    /**
     *
     * @author blj0011
     */
    public class JavaFXApplication342 extends Application
    {
    
        @Override
        public void start(Stage primaryStage)
        {
            List<LevelMeter2> levelMeter2s = new ArrayList();
            List<Timeline> metersTimelines = new ArrayList();
            for (int i = 0; i < 9; i++) {
                LevelMeter2 levelMeter2 = new LevelMeter2(300, 30);
                levelMeter2s.add(levelMeter2);
                metersTimelines.add(levelMeter2.getTimeline());
            }
    
            ParallelTransition parallelTransition = new ParallelTransition();
            parallelTransition.getChildren().addAll(metersTimelines);
    
            Button button = new Button("Start");
            button.setOnAction((event) -> {
                switch (button.getText()) {
                    case "Start":
                        parallelTransition.play();
                        button.setText("Stop");
                        break;
                    case "Stop":
                        parallelTransition.stop();
                        button.setText("Start");
                        break;
                }
            });
    
            HBox hBox = new HBox();
            hBox.getChildren().addAll(levelMeter2s);
    
            VBox vBox = new VBox(hBox, new StackPane(button));
            Scene scene = new Scene(vBox, 300, 350);
            primaryStage.setScene(scene);
            primaryStage.setTitle("Test IHM");
            primaryStage.setOnCloseRequest(event -> {
                System.out.println("FIN");
                System.exit(0);
            });
            primaryStage.show();
        }
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args)
        {
            launch(args);
        }
    
    }
    

    LevelMeter2

    import javafx.animation.KeyFrame;
    import javafx.animation.Timeline;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.scene.Parent;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.transform.Rotate;
    import javafx.util.Duration;
    
    public final class LevelMeter2 extends Parent
    {
    
        private final IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
    
        Timeline timeline;
        double value = -20;
    
        private final Rectangle led;
    
        private final IntegerProperty height = new SimpleIntegerProperty();
        private final IntegerProperty width = new SimpleIntegerProperty();
        private final DoubleProperty linearValue = new SimpleDoubleProperty();
        private final Color backgroundColor = Color.BLACK;
        private final double minLinearValue;
        private final double maxLinearValue;
    
        public LevelMeter2(int height2, int width2)
        {
            this.height.set(height2);
            this.levelMeterHeight.bind(height.multiply(0.9));
            this.width.set(width2);
    
            linearValue.set(1.0);
            minLinearValue = Math.pow(10, -60.0 / 100);
            maxLinearValue = Math.pow(10, 3.0 / 100) - minLinearValue;
    
            Rectangle levelMeterShape = new Rectangle();
            levelMeterShape.widthProperty().bind(width);
            levelMeterShape.heightProperty().bind(height);
            levelMeterShape.setStroke(backgroundColor);
    
            this.getChildren().add(levelMeterShape);
    
            led = new Rectangle();
            led.widthProperty().bind(width.multiply(0.8));
            led.translateXProperty().bind(width.multiply(0.1));
            led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
            led.setFill(Color.AQUA);
            Rotate rotate = new Rotate();
            rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
            rotate.pivotYProperty().bind(height.divide(2));
            rotate.setAngle(180);
            led.getTransforms().add(rotate);
            getChildren().add(led);
    
            timeline = new Timeline(new KeyFrame(Duration.millis(25), (event) -> {
                setValue(value);
    
                value = (Math.random() - 0.5) * 10 + value;
                if (value > 3) {
                    value = 3;
                }
                if (value < -60) {
                    value = -60;
                }
    
            }));
            timeline.setCycleCount(Timeline.INDEFINITE);
        }
    
        public double convertdBToLinearValue(double dB)
        {
            return ((double) Math.round(100 * ((Math.pow(10, dB / 100) - minLinearValue) / maxLinearValue))) / 100;
        }
    
        public double convertLinearValueTodB(double linearValue)
        {
            return 100 * Math.log10(linearValue * maxLinearValue + minLinearValue);
        }
    
        public void setValue(double dB)
        {
            if (dB > 3) {
                dB = 3;
            }
            linearValue.setValue(convertdBToLinearValue(dB));
        }
    
        public void startAnimation()
        {
            timeline.play();
        }
    
        public void stopAnimation()
        {
            timeline.stop();
        }
    
        public Timeline getTimeline()
        {
            return timeline;
        }
    }
    

    【讨论】:

      【解决方案2】:

      您的run() 实现似乎正在从后台线程更新场景图。如Concurrency in JavaFX 中所述:

      JavaFX 场景图……不是线程安全的,只能从 UI 线程(也称为 JavaFX 应用程序线程)访问和修改。在 JavaFX 应用程序线程上实现长时间运行的任务不可避免地会使应用程序 UI 无响应。”

      改为使用Task,图示here 和here。您的call() 实现可以异步收集数据并通过updateValue() 通知GUI 当前状态。然后您的valueProperty() 侦听器可以安全地调用setValue()。因为“合并更新以防止 FX 事件队列饱和”,您的应用程序即使在较旧的硬件上也能令人满意地执行。

      或者,如果您的音频源是supported Media 类型之一,则此AudioBarChartApp 会更新AudioSpectrumListener 中的BarChart 的数据模型,该AudioSpectrumListener 已使用相应的MediaPlayer 注册。下图显示pink noise。

      private XYChart.Data<String, Number>[] series1Data;
      …
      audioSpectrumListener = (double timestamp, double duration,
                               float[] magnitudes, float[] phases) -> {
          for (int i = 0; i < series1Data.length; i++) {
              series1Data[i].setYValue(magnitudes[i] + 60);
          }
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-05-21
        • 2012-06-18
        • 1970-01-01
        • 2022-09-01
        • 2015-09-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多