Воспроизведение игровой музыки на Java — Как остановить поток данных?

#java #audio

#java #Аудио

Вопрос:

У меня есть урок музыки для игры, которую я создаю, на вводном курсе в средней школе. Вы можете увидеть источник, откуда я взял код.

Изначально я использовал Clip , но обнаружил, что у него довольно маленькие размеры буфера, и он не может хорошо воспроизводить длинные песни (по крайней мере, не на наших школьных компьютерах Mac). Новый код работает хорошо, насколько я понимаю, он получает «фрагменты» песни, воспроизводит их, затем получает еще фрагменты. Проблема в том, что при смене музыки песни иногда перекрываются, и когда я выхожу из игры, воспроизводится ужасный звук (по крайней мере, на компьютерах Mac), вероятно, вызванный тем, что поток данных прерывается перед закрытием игры.

Есть ли какой-либо способ исправить это, кроме задержки программы на несколько секунд каждый раз? (Примечание: я не хочу использовать внешние файлы .jar или библиотеки, я бы хотел, чтобы это было строго в JRE)

 package mahaffeyfinalproject;

import java.io.File;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;


public class Music implements Runnable{

//SOURCE: http://www.ntu.edu.sg/home/ehchua/programming/java/J8c_PlayingSound.html
//Note: I've modified it slightly

private String location;
private boolean play;

public void Music(){

}

public void playMusic(String loc) {
    location = loc;
    play = true;
    try{
        Thread t = new Thread(this);
        t.start();
    }catch(Exception e){
        System.err.println("Could not start music thread");
    }
}

public void run(){
    SourceDataLine soundLine = null;
    int BUFFER_SIZE = 64*1024;

    try {
        File soundFile = new File(location);
        AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(soundFile);
        AudioFormat audioFormat = audioInputStream.getFormat();
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
        soundLine = (SourceDataLine) AudioSystem.getLine(info);
        soundLine.open(audioFormat);
        soundLine.start();
        int nBytesRead = 0;
        byte[] sampledData = new byte[BUFFER_SIZE];

        while (nBytesRead != -1 amp;amp; play == true) {
            nBytesRead = audioInputStream.read(sampledData, 0, sampledData.length);
            if (nBytesRead >= 0) {
               soundLine.write(sampledData, 0, nBytesRead);
            }
        }
    } catch (Exception e) {
        System.err.println("Could not start music!");
    }

    soundLine.drain();
    soundLine.close();

}

public void stop(){
    play = false;
}


}
  

Комментарии:

1. 1) Я слышал, что звук Java проблематичен на Mac. 2) playMusic(String loc) Я содрогаюсь, когда вижу String аргументы, которые представляют File объекты. Если это File то, что требуется, не принимайте никаких других. 3) И хотя по этому вопросу, в большинстве случаев an InputStream в качестве аргумента превосходит File . InputStream Может исходить из File или URL (которое может включать ресурсы в архивах) или ByteArrayInputStream создаваться в памяти.

2. 1. Да, это так. В целом Clip не так хорош и имеет очень небольшие ограничения на объем данных, которые вы можете ему передать, потому что он полностью сохраняет их в памяти. На ПК у меня не было проблем, но на Mac у меня возникали проблемы, связанные с размером. 2. Извините, я все еще очень новичок, мне 17 лет, у меня нет реального формального образования (вводный курс в средней школе вряд ли считается) — большинству вещей мне приходилось учиться самому, я запомню это. 3. Действительно, я стараюсь быть максимально эффективным, даже если мне приходится переписывать проект, и это определенно лучший способ сделать это, хотя в игре это несколько необязательно.

Ответ №1:

Я не уверен насчет ужасного звука, который вы получаете при закрытии, но я думаю, что у меня есть идея о том, как решить проблему перекрытия, о которой вы упомянули. Похоже, что вы используете Sound API нормально, но я подозреваю, что у вас может возникнуть проблема с параллелизмом. Я заметил, что вы запускаете новый поток для каждого воспроизводимого звука. Это хорошая идея, но вам нужно быть осторожным, если вы не хотите, чтобы ваши песни микшировались. Моя теория заключается в том, что ваш код выглядит примерно так:

 Music songA = new Music();
Music songB = new Music();

//Start playing song A
songA.playMusic("path");

//Some stuff happens with your game...

//Some time later you want to change to song B
songA.stop();
songB.playMusic("other path");
  

На первый взгляд это выглядит нормально, в конце концов, вы были осторожны, чтобы остановить songA перед запуском songB, верно? К сожалению, когда дело доходит до параллелизма, немногие вещи настолько просты, как нам хотелось бы. Давайте посмотрим на небольшую часть вашего кода, в частности на while цикл, отвечающий за подачу данных в аудиопоток…

 //Inside run(), this is the interesting bit at the end

while(nBytesRead != -1 amp;amp; play == true){
    //Feed the audio stream while there is still data 
}
  

stop() устанавливает play значение false, вызывая завершение цикла while, после завершения текущей итерации. Если я не ошибаюсь, каждая итерация вашего while цикла отправляет 64 кб данных в аудиопоток. 64 кб — это достаточное количество аудиоданных, и в зависимости от свойств звука это будет продолжаться некоторое время. Что вы действительно хотите сделать, это подать потоку сигнал о завершении (т. Е. установить для воспроизведения значение false), а затем дождаться завершения потока. Я полагаю, что это устранит проблему перекрытия, и если вы не завершаете свои потоки должным образом, эта проблема также может вызывать ужасный шум при завершении. Я смиренно предлагаю следующее изменение в вашем классе музыки (без гарантии правильности)…

 public class Music implements Runnable{
    private Thread t;    //Make the thread object a class field

    //Just about everything is the same as it was before...

    public void stop(){
        play = false;
        try{
            t.join()
        }catch(InterruptedException e){
            //Don't do anything, it doesn't matter
        }
   }
}
  

Надеюсь, это поможет, и ради всего хорошего не используйте мой код как есть, он пришел мне в голову примерно за 2 минуты. Я рекомендую серию обучающих программ по Java, посвященных потоковой обработке.

Комментарии:

1. Спасибо за ответ! Проект должен был быть завершен сегодня (должен был быть завершен вчера), поэтому я, к сожалению, не смог попробовать ваше решение. Путем очистки звуковой линии (и пары других вещей) Я смог остановить звук. Вы правы насчет того, почему это перекрывалось, звук просто не прекратился до начала воспроизведения следующего. Ужасный звук при закрытии, я думаю , был вызван тем, что воспроизведение звука не было закончено при выходе. С лучшим способом выхода и небольшим ожиданием в 200 миллисекунд в конце, казалось, все было в порядке, если не нажать кнопку X в верхнем левом углу и принудительно закрыть ее.