为什么我的Java应用程序不在每个循环中播放声音?

问题描述 投票:0回答:2

我正在尝试创建一个模拟人在其键盘上打字的Java应用程序。击键声音以可变间隔(模拟真实的人打字)循环播放(Java随机选择击键声音并进行播放)。

开始时效果很好,但是在95th迭代之后,它停止播放声音(同时仍然循环播放)少于4秒,然后再次播放声音。在160th迭代之后,它几乎每秒播放一次声音(而不是每秒的三分之一到六分之一)。一段时间后,它会长时间停止播放声音,然后永远停止播放。

这里是AudioPlayer.java类的来源:

package entity;

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class AudioPlayer implements Runnable {
    private String audioFilePath;

    public void setAudioFilePath(String audioFilePath) {
         this.audioFilePath = audioFilePath;
    }

    @Override
    public void run() {
        File audioFile = new File(audioFilePath);

        try {
            AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile);
            AudioFormat format = audioStream.getFormat();
            DataLine.Info info = new DataLine.Info(Clip.class, format);
            Clip audioClip = (Clip) AudioSystem.getLine(info);
            audioClip.open(audioStream);
            audioClip.start();
            boolean playCompleted = false;
            while (!playCompleted) {
                try {
                    Thread.sleep(500);
                    playCompleted = true;
                }
                catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
            audioClip.close();
        } catch (UnsupportedAudioFileException ex) {
            System.out.println("The specified audio file is not supported.");
            ex.printStackTrace();
        } catch (LineUnavailableException ex) {
            System.out.println("Audio line for playing back is unavailable.");
            ex.printStackTrace();
        } catch (IOException ex) {
            System.out.println("Error playing the audio file.");
            ex.printStackTrace();
        }
    }
}

这是Main.java类,用于测试击键模拟器:

package sandbox;

import java.util.Random;

import entity.AudioPlayer;

public class Main {
    public static void main(String[] args) {
        Random rnd = new Random();
        AudioPlayer audio;

        for(int i = 0; i < 10000; i++) {
            int delay = rnd.nextInt(200)+75;
            try {
                Thread.sleep(delay);
            }
            catch (InterruptedException ie) {}
            int index = rnd.nextInt(3)+1;
            audio = new AudioPlayer();
            audio.setAudioFilePath("resources/keystroke-0"+index+".wav");
            Thread thread = new Thread(audio);
            thread.start();
            System.out.println("iteration "+i);
        }
    }
}

我在资源目录中全部使用了多个不同的敲击声(总共3个)的多个短波(小于200ms)文件。

编辑

我阅读了您的答案和评论。而且我在想,也许因为建议的解决方案不起作用而误解了它们,或者也许我应该明确说明我真正想要的是什么。另外,我需要注意的是,我不经常使用线程(也不知道什么是互斥体)。

因此,我将首先解释我到底要程序执行的操作。它应该能够模拟击键,因此我使用了Thread,因为它允许两种击键声音重叠,就像当真人在打字时一样。基本上,我使用的声音片段是按键声音,而按键声音由两种声音组成:按键的声音。释放钥匙的声音。

如果程序在某个时候允许两个击键重叠,则听起来好像有人先按下一个键,然后又按下另一个键,然后释放第一个键。这就是真正听起来像打字的感觉!

现在使用建议的解决方案遇到的问题是:直接调用AudioPlayer的run()方法时,

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

    while (running) {
        Date previous = new Date();
        Date delay = new Date(previous.getTime()+rnd.nextInt(300)+75);

        // Setting the audio here

        audio.run();
        Date now = new Date();

        if (now.before(delay)) {
            try { 
                Thread.sleep(delay.getTime()-now.getTime());
            } catch (InterruptedException e) {
            }
        }

        System.out.println("iteration: "+(++i));
    }
}

声音按顺序播放(一个接一个),并且播放速率取决于AudioPlayer的睡眠持续时间(或者,如果main()方法的延迟大于AudioPlayer的睡眠持续时间,则取决于延迟) ,这是不好的,因为它听起来不会像一般的打字员(更像是刚接触打字并在打字时仍在寻找每个按键的人)。

当调用AudioPlayer的线程的join()方法时,

public static void main(String[] args)
{
    //Variable definitions here

    while (running) {
        int delay = rnd.nextInt(200)+75;
        try
        {
            Thread.sleep(delay);
        }
        catch (InterruptedException ie)
        {

        }

        //Setting the AudioPlayer and creating its Thread here

        thread.start();
        try
        {
            thread.join();
        }
        catch(InterruptedException ie)
        {

        }
        System.out.println("iteration "+(++i));
    }
}

声音也按顺序播放,并且播放速率取决于AudioPlayer的睡眠持续时间(或者,如果main()方法中的延迟高于AudioPlayer的睡眠持续时间,则取决于延迟),出于与以前相同的原因是不好的。

因此,回答评论者的问题之一。是!还有其他一些未表达的问题,这些问题首先需要使用线程。

我发现了一种解决方法,可以“解决”我的问题(但是我不认为这是一种适当的解决方案,因为我在某种程度上是在作弊):我所做的是将AudioPlayer的睡眠时间延长到在程序停止(24小时)之前不太可能达到,并且据我所知,即使超过一个小时,它也不会占用太多资源。

[您可以查看我想要的东西,运行建议的解决方案时得到的东西,以及我在this youtube videos上使用的变通方法所得到的东西(不幸的是,StackOverflow没有视频上传功能。因此我不得不将其放到youtube上)。

java multithreading audio keystroke
2个回答
0
投票

关于线程,您正在谈论独立的执行流程。您的程序设计为使延迟的选择与声音的播放不是独立的。

类似

    for (int i = 0; i < 10000; i++) {
        int delay = rnd.nextInt(200)+75;
        try {
            Thread.sleep(delay);
        } catch (InterruptedException ie) {

        }
        int index = rnd.nextInt(3)+1;
        audio = new AudioPlayer();
        audio.setAudioFilePath("resources/keystroke-0"+index+".wav");
        audio.run();
        System.out.println("iteration "+i);
    }

表示您先“等待”,然后“运行wav”,然后“重复”。

现在,您依赖于内核上一致的执行线程调度来获得所需的结果;除了线程不是要表达一致的执行计划的方式之外。线程旨在成为表达独立的执行调度的方式。

赔率介于您当前的等待和当前的“播放波形”之间,还有其他一些情况,并且如果有足够的时间,甚至wav文件都可能会无序播放。我会明确显示订购顺序。

如果您需要很好地控制循环中的时间,请查看游戏循环类型设置。它类似于您的for循环,但看起来像

while (running) {
  SomeTimeType previous = new TimeType();
  SomeTimeOffset delay = new TimeOffset(rnd.nextInt(200)+75);

  ...
  audio.run();
  SomeTimeType now = new TimeType();
  if (now.minus(offset).compareTo(previous) > 0) {
    try { 
      Thread.sleep(now.minus(offset).toMillis())
    } catch (InterruptedException e) {
    }
  }
}

这里的主要区别是您的随机延迟是从wav文件的播放时间开始到下一个wav文件的播放时间的开始,并且如果延迟时间短于wav文件的播放时间,则文件之间没有延迟。 。

此外,我会研究AudioPlayer是否可以在波形文件回放之间重用,因为这可能会为您带来更好的结果。

现在您确实需要将播放保持在单独的线程中,您需要将循环的线程与AudioPlayer的线程连接起来,以确保AudioPlayer在循环线程前进之前完成。即使您在for循环中等待了更长的时间,请记住,任何进程都可以随时从CPU内核中退出,因此您的等待并不能保证for循环在每次迭代中花费的时间比AudioPlayer在每次wav中花费的时间更多文件,如果CPU必须处理其他事情(例如网络数据包)。

    for (int i = 0; i < 10000; i++) {
        int delay = rnd.nextInt(200)+75;
        try {
            Thread.sleep(delay);
        } catch (InterruptedException ie) {

        }
        int index = rnd.nextInt(3)+1;
        audio = new AudioPlayer();
        audio.setAudioFilePath("resources/keystroke-0"+index+".wav");
        Thread thread = new Thread(audio);
        thread.start();
        thread.join();
        System.out.println("iteration "+i);
    }

thread.join()将强制for循环进入睡眠状态(可能将其移离CPU),直到音频线程完成为止。


0
投票

除了EdwinBuck所说的话,我相信您正在(并且每次)在AudioPlayer类中做很多工作。尝试一次为所有音频文件预创建一个AudioPlayer实例(我相信它是4个?),并添加一个单独的play()方法,以便在循环内可以执行诸如audioplayers [index] .play( )。

还要注意,在AudioPlayer类中,您等待500毫秒以使声音结束,这比您等待播放下一个声音的时间更长。一段时间后,这将导致可用线程用完...也许在AudioClip完成时可以使用回调,而不是等待。

© www.soinside.com 2019 - 2024. All rights reserved.