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