Почему мое приложение Java не воспроизводит звук на каждом l oop? - PullRequest
0 голосов
/ 11 апреля 2020

Я пытаюсь создать приложение Java, которое имитирует ввод текста с клавиатуры. Звук нажатия клавиш воспроизводится в al oop (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);
        }
    }
}

Я использовал несколько коротких (менее 200 мс) волновых файлов с различными звуковыми нажатиями клавиш (всего 3) в каталоге ресурсов.

РЕДАКТИРОВАТЬ

Я прочитал ваши ответы и Комментарии. И я думаю, что, может быть, я их неправильно понял, потому что предложенные решения не работают, или, может быть, мне следовало четко заявить о том, чего именно я хотел. Кроме того, я должен отметить, что я не часто использую потоки (и понятия не имею, что такое мьютекс).

Поэтому я сначала объясню, что именно я хочу, чтобы программа делала. Он должен иметь возможность имитировать нажатие клавиш, и поэтому я использовал поток, потому что он позволяет перекрывать два звука нажатия клавиш, как при наборе текста настоящим человеком. По сути, используемые мной звуковые клипы - это звуки нажатия клавиш, а звук нажатия клавиш состоит из двух звуков: звука нажатия клавиши. звук нажатия клавиши.

Если в какой-то момент программа позволяет перекрывать два нажатия клавиш, это будет звучать так, как будто кто-то нажал одну клавишу, а затем другую, а затем отпустил первую клавишу. Вот как на самом деле печатается звук!

Теперь проблемы, с которыми я столкнулся при использовании предложенных решений: при непосредственном вызове метода run () AudioPlayer

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

При вызове метода join () потока AudioPlayer,

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 часа) и из того, что я видел, он не использует много ресурсов даже после более чем час.

Вы можете проверить, что я хочу, что я получаю при запуске предлагаемых решений и что я получаю, используя мой обходной путь на этом видео на YouTube (К сожалению, StackOverflow не имеет функция загрузки видео. поэтому мне пришлось поставить его на YouTube).

РЕДАКТИРОВАТЬ Звуковые эффекты можно скачать здесь .

Ответы [ 4 ]

1 голос
/ 16 апреля 2020

Помимо того, что сказал Эдвин Бак, я полагаю, что вы делаете много работы в своем классе AudioPlayer (и каждый раз). Попробуйте предварительно создать экземпляр AudioPlayer один раз для всех ваших аудиофайлов (я думаю, что это 4?) И добавьте отдельный метод play (), чтобы внутри вашего l oop вы могли делать что-то вроде аудиоплеера [index] .play ().

Также обратите внимание, что в вашем классе AudioPlayer вы ожидаете 500 мсек, пока звук не достигнет sh, что больше, чем вы ожидаете, чтобы воспроизвести следующий звук. Через некоторое время это приведет к тому, что у вас закончатся доступные потоки ... возможно, существует обратный вызов, который вы можете использовать после завершения AudioClip вместо ожидания.

1 голос
/ 18 апреля 2020

Как насчет этого однопоточного решения, которое является вашей более чистой версией, но с повторным использованием уже открытых клипов из буферов? Для меня печатание звучит довольно естественно, хотя нет двух звуков, играющих одновременно. Вы можете настроить скорость набора текста, изменив соответствующие постоянные c в классе Application.

package de.scrum_master.stackoverflow.q61159885;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine.Info;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static javax.sound.sampled.AudioSystem.getAudioInputStream;
import static javax.sound.sampled.AudioSystem.getLine;

public class AudioPlayer implements Closeable {
  private final Map<String, Clip> bufferedClips = new HashMap<>();

  public void play(String audioFilePath) throws IOException, UnsupportedAudioFileException, LineUnavailableException {
    Clip clip = bufferedClips.get(audioFilePath);
    if (clip == null) {
      AudioFormat audioFormat = getAudioInputStream(new File(audioFilePath)).getFormat();
      Info lineInfo = new Info(Clip.class, audioFormat);
      clip = (Clip) getLine(lineInfo);
      bufferedClips.put(audioFilePath, clip);
      clip.open(getAudioInputStream(new File(audioFilePath)));
    }
    clip.setMicrosecondPosition(0);
    clip.start();
  }

  @Override
  public void close() {
    bufferedClips.values().forEach(Clip::close);
  }
}
package de.scrum_master.stackoverflow.q61159885;

import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.IOException;
import java.util.Random;

public class Application {
  private static final Random RANDOM = new Random();

  private static final int ITERATIONS = 10000;
  private static final int MINIMUM_WAIT = 75;
  private static final int MAX_RANDOM_WAIT = 200;

  public static void main(String[] args) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    try (AudioPlayer audioPlayer = new AudioPlayer()) {
      for (int i = 0; i < ITERATIONS; i++) {
        sleep(MINIMUM_WAIT + RANDOM.nextInt(MAX_RANDOM_WAIT));
        audioPlayer.play(randomAudioFile());
      }
    }
  }

  private static void sleep(int delay) {
    try {
      Thread.sleep(delay);
    } catch (InterruptedException ignored) {}
  }

  private static String randomAudioFile() {
    return "resources/keystroke-0" + (RANDOM.nextInt(3) + 1) + ".wav";
  }
}

Возможно, вы заметили, что AudioPlayer равно Closeable, то есть вы можно использовать «попробовать с ресурсами» в вызывающем приложении. Таким образом, он гарантирует, что в конце программы все клипы будут закрыты автоматически.

Ключ к воспроизведению того же клипа, конечно, clip.setMicrosecondPosition(0) перед началом.


Обновление: Если вы хотите смоделировать несколько человек, просто измените основной класс следующим образом. Кстати, я ничего не знаю о звуковом программировании и о том, есть ли способ лучше справляться с микшерами и перекрывающимися звуками. Это просто доказательство концепции, чтобы дать вам идею. Существует один поток на человека, но каждый человек печатает последовательно, а не два ключа одновременно. Но несколько человек могут перекрываться, потому что есть один AudioPlayer на человека с его собственным набором буферизованных клипов.

package de.scrum_master.stackoverflow.q61159885;

import java.util.Random;

public class Application {
  private static final Random RANDOM = new Random();

  private static final int PERSONS = 2;
  private static final int ITERATIONS = 10000;
  private static final int MINIMUM_WAIT = 150;
  private static final int MAX_RANDOM_WAIT = 200;

  public static void main(String[] args) {
    for (int p = 0; p < PERSONS; p++)
      new Thread(() -> {
        try (AudioPlayer audioPlayer = new AudioPlayer()) {
          for (int i = 0; i < ITERATIONS; i++) {
            sleep(MINIMUM_WAIT + RANDOM.nextInt(MAX_RANDOM_WAIT));
            audioPlayer.play(randomAudioFile());
          }
        } catch (Exception ignored) {}
      }).start();
  }

  private static void sleep(int delay) {
    try {
      Thread.sleep(delay);
    } catch (InterruptedException ignored) {}
  }

  private static String randomAudioFile() {
    return "resources/keystroke-0" + (RANDOM.nextInt(3) + 1) + ".wav";
  }
}
0 голосов
/ 19 апреля 2020

Библиотека AudioCue была создана именно для такого рода вещей. Вы можете попробовать запустить демонстрацию «Лягушачий пруд», имитируя множество лягушатых лягушек, сгенерированных из одной записи лягушачьего крика.

Вы можете сделать один клик на машинке и запустить все с нее, создать реплику допустим, допустимо 10 одновременных перекрытий. Затем используйте ГСЧ, чтобы выбрать, какой из 10 «курсоров» нажать. Каждый из 10 машинисток может иметь свою собственную громкость и положение панорамирования и может быть немного различен, так что звучит так, будто пишущие машинки - это разные модели, или клавиши нажимаются с разным весом (как если бы это была старая ручная пишущая машинка).

Можно настроить алгоритмы ГСЧ для различных скоростей набора (используя различное время ожидания).

Для себя я написал систему событий, в которой команды воспроизведения ставятся в очередь в системе событий, используя ConcurrentSkipListSet, где хранимые объекты включают значение времени (миллисекунды после заданной нулевой точки), которое используется для сортировки, а также контроля, когда игра будет выполнена. Это может быть излишним, если вы не собираетесь делать такие вещи очень часто.

0 голосов
/ 14 апреля 2020

С потоками вы говорите о независимых потоках выполнения. Ваша программа разработана таким образом, что выбор задержки и воспроизведения звука не являются независимыми.

что-то вроде

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

express что вы "подождите", затем "запустите wav", затем "повторите".

Сейчас вы полагаетесь на согласованное планирование потоков исполнения на ядрах для получения желаемого результата; за исключением того, что потоки не предназначены для express согласованного планирования выполнения. Потоки предназначены для express независимого планирования выполнения.

Шансы находятся между вашим текущим ожиданием и текущим "игрой на волне", между которыми есть несколько других вещей, и с достаточным количеством времени, возможно даже файлы WAV могут воспроизводиться не по порядку. Я бы сделал порядок явным.

Если вам нужен хороший контроль над временем в пределах l oop, обратитесь к игровой настройке типа l oop. Это похоже на ваш for l oop, но выглядит как

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 между воспроизведением волнового файла, так как это будет возможно, вы получите еще лучшие результаты.

Теперь, если есть вероятность, что вам действительно нужно сохранить воспроизведение в отдельном потоке, вам нужно присоединить поток l oop к потоку AudioPlayer, чтобы что AudioPlayer завершает работу до того, как продвигается поток l oop. Даже если вы дольше ждете в течение l oop, помните, что любой процесс может оторваться от ядра ЦП в любое время, поэтому ваше ожидание не является гарантией того, что для l oop требуется больше времени на итерацию чем AudioPlayer принимает на файл wav, если процессор должен был обрабатывать что-то еще (например, сетевой пакет).

    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 для l oop к go в состояние сна (возможно, смещение его с процессора), пока аудиопоток не завершится.

...