Высокая частота обновления в JavaFX - PullRequest
0 голосов
/ 28 февраля 2019

Я пытаюсь написать программу с эквалайзером, частотным анализатором и измерителем уровня звука.Модельная часть, кажется, работает очень хорошо, но я экспериментирую с ошибками с 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?Или как я могу предотвратить замерзание?

Спасибо за помощь.

Ответы [ 2 ]

0 голосов
/ 28 февраля 2019

Я бы посоветовал вам отойти от Threads и использовать что-нибудь из пакета JavaFX Animation.В этом примере используется Timeline.Этот код настроен для работы со скоростью около 60 кадров в секунду.Вы можете настроить это значение, используя 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 Пример:

Main

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;
    }
}
0 голосов
/ 28 февраля 2019

Ваша реализация run(), по-видимому, обновляет граф сцены из фонового потока.Как обсуждено в Параллелизм в JavaFX :

Граф сцены JavaFX… не является потокобезопасным и может быть доступен и изменен только из потока пользовательского интерфейса.известный как поток приложения JavaFX.Реализация длительных задач в потоке приложения JavaFX неизбежно приводит к тому, что пользовательский интерфейс приложения не отвечает. "

Вместо этого используйте Task, здесь показано и здесь . Ваша реализация call() может собирать данные асинхронно и уведомлять GUI о текущем состоянии через updateValue(). Ваш valueProperty() слушатель может затем безопасно вызывать setValue(). Потому что "Обновления объединяются впредотвращать насыщение очереди событий FX ", ваше приложение будет работать удовлетворительно даже на старом оборудовании.

В качестве альтернативы, если ваш источник звука является одним из поддерживаемых Media типы AudioBarChartApp обновляет модель данных BarChart в AudioSpectrumListener, зарегистрированном в соответствующем MediaPlayer. Изображение нижеотображает розовый шум .

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);
    }
};

pink noise

...