Animation 在JavaFx中更新应用程序线程以绘制节点

Animation 在JavaFx中更新应用程序线程以绘制节点,animation,javafx,audio,audio-streaming,javax.sound.sampled,Animation,Javafx,Audio,Audio Streaming,Javax.sound.sampled,我正在开发一个程序,可以使用麦克风或线路输入的流式振幅数据绘制音频波形。我认为这样做的方法是以与采样率相等的速率从采样数据中绘制每个点,在x方向上绘制每个点,每绘制一步。因此,我需要每秒更新JavaFx应用程序线程44100次,以绘制每个点。在我开始做这件事之前,我想通过画一条直线,每半秒钟更新一次每个点来测试我的想法。我正在使用Timeline类来实现这一点。我的代码如下所示: public class JavaFxPractice extends Application { priva

我正在开发一个程序,可以使用麦克风或线路输入的流式振幅数据绘制音频波形。我认为这样做的方法是以与采样率相等的速率从采样数据中绘制每个点,在x方向上绘制每个点,每绘制一步。因此,我需要每秒更新JavaFx应用程序线程44100次,以绘制每个点。在我开始做这件事之前,我想通过画一条直线,每半秒钟更新一次每个点来测试我的想法。我正在使用Timeline类来实现这一点。我的代码如下所示:

public class JavaFxPractice extends Application { 
  private int xValue = 50;

  @Override 
  public void start(Stage primaryStage) {      
    Pane pane = new Pane();

    EventHandler<ActionEvent> eventHandler = e -> {
      xValue++;
      Line point = new Line(xValue,50,xValue,50);
      pane.getChildren().add(point);
    };

    Timeline animation = new Timeline(new KeyFrame(Duration.millis((500)), eventHandler)); 
    animation.setCycleCount(500);
    animation.play();

    Scene scene = new Scene(pane, 600, 500);
    primaryStage.setTitle("Streaming Test");
    primaryStage.setScene(scene);
    primaryStage.show();  
  } 
}
公共类JavaFxPractice扩展应用程序{
私有int xValue=50;
@凌驾
公共无效开始(阶段初始阶段){
窗格=新窗格();
EventHandler EventHandler=e->{
xValue++;
线点=新线(xValue,50,xValue,50);
pane.getChildren().add(点);
};
时间线动画=新的时间线(新的关键帧(Duration.millis((500)),eventHandler));
动画。setCycleCount(500);
动画。播放();
场景=新场景(窗格,600500);
primaryStage.setTitle(“流式测试”);
初级阶段。场景(场景);
primaryStage.show();
} 
}

然而,每次我这样做,我的程序就会失去响应,我不得不强制关闭它。我注意到,如果我做同样的事情,但相反,让文本闪烁和关闭,它的工作非常好。是否存在无法使用Timeline类绘制线的原因?这会不会给线程带来太多的负载?如果是这样,我可以用什么方法来解决我的想法。我只想能够实时绘制波形,每秒更新44100次左右。

对于任何正在进行的动画,我建议使用
AnimationTimer
。它试图以尽可能接近60 fps的速度进行更新。(我只是在第一篇文章之后读了一些评论,推荐
AnimationTimer
。这些人是对的。我想知道为什么他们自己没有把它作为一个答案。)

然后问题就变成了显示什么。如果我要解决这个问题,我会尝试以下方法:

  • 制作一个数组以保持显示大小的位置,每像素一个桶
  • 创建一个函数,从这样的数组中提取数据,动画计时器可以调用该数组
  • 创建一个并发安全队列来保存这些数组(例如,
    ConcurrentLinkedQueue
  • 加载
    ConcurrentLinkedQueue
    ,读取您的
    AudioInputLine
  • AnimationTimer

  • 要计算出计时,您可能需要使用抽取(例如,每2秒或3个或更多PCM数据点中的2个),或者如果所需的抽取结果不是易于使用的有理分数,则需要使用线性插值。换句话说,阵列(与像素相关)和PCM数据点之间不存在1:1对应关系。您使用的抽取次数越多,丢失的高频就会越多。

    我无法重现您在执行提供的示例时描述的冻结问题。但是请记住,您的代码最终会向UI添加500个
    对象;最好只更新相同的
    ,以新的x坐标结束。至于以每秒44100次左右的速度更新UI,如果这需要使用
    平台发布一个新的runnable。请注意,每次更新都要使用runLater
    ,这将淹没FX线程,并可能(可能)导致UI冻结。最重要的是,JavaFX通常以每秒60帧的速度运行,并且尝试更新速度超过这一速度将不会产生明显的效果。您当前的代码对我来说也运行得很好——但我要重申上面针对您建议的实际应用程序所表达的担忧。简单地将多个节点添加到场景图中可能会导致性能问题。您可能想考虑一种策略,例如在后台线程中更新屏幕以外的内容,并在每次FX场景图刷新时使用<代码>动画定时器< /代码>来获取最新的更新。使用
    Canvas
    WritableImage
    可能是比添加这么多节点更好的解决方案,除非您可以通过更新现有节点来实现这一点。最近相关的问题(注释中有其他有用的链接):我理解这只是一个示例,但是重用现有节点和使用尽可能少的节点的概念可能会转移到实际代码中。关于波形的更新率,它不需要每秒更新41000次。首先,世界上没有一款显示器的刷新率接近41000Hz,因此即使软件/GPU能够/允许刷新率,显示器也无法显示。另一方面,没有人能感知41000fps。正如@James(查看他发布的链接)所建议的那样,使用
    AnimationTimer
    ,背后的想法是它可以轮询最新数据并更新用户界面。它会以用户界面能够处理的最快速度(例如60fps)完成这项工作。当计时器执行此操作时,您的后台线程可以以您希望的速度更新模型状态。但轮询之间(即帧之间)的更新将被删除。基本上,
    AnimationTimer
    以60Hz的频率对传入数据进行采样。