从 JAR 应用程序播放音频文件在 Linux 上不起作用
Playing audio files from a JAR-application does not work on Linux
我的 Java 申请需要一些帮助。它的用途是阅读某个网站,所以我需要连续播放很多音频文件。 JAR 是使用 Java 8 编译的。我用 Windows 11 和 Java 16.0.1 测试了我的应用程序,一切正常。然后我使用了最新的 Ubuntu Linux 和 Java 11.0.13 以及 Java 8:它播放了一些音频,但不是每个文件。
我写了一个测试 class,结果是,无论我以何种顺序播放音频,都只播放前(正是!)62 个文件。每个下一个文件(甚至是最初成功播放的文件)都会产生我的代码在这个位置抛出的异常:
if (mixerSelected != null) {
audioClip0 = AudioSystem.getClip(mixerSelected);
} else {
throw new IllegalArgumentException("File is not compatible: '" + audioFilePath + "'.");
}
我确保每个音频文件都是 .WAV
和
- 8k采样率,
- 平均每秒 16k 字节,
- 16 位,并且
- pcm_s16le编解码器。
我的应用程序构建为 JAR 文件,包括资源目录中的音频文件。
这是我的代码:
public class PlayAudio {
/**
* plays an audio file
*
* @param audioFilePath String: path to the audio file
* @param speed double: speed applied to the audios
*/
public boolean singleFile(String audioFilePath, double speed) {
//audioFilePath = "audio" + File.separator + audioFilePath;
audioFilePath = "audio" + "/" + audioFilePath;
AudioInputStream audioStream0;
//create new file using path to the audio
try {
//load files from resources folder as stream
ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream(audioFilePath);
InputStream bufferedInputStream = new BufferedInputStream(inputStream);
if (bufferedInputStream == null) {
throw new IllegalArgumentException("File not found: '" + audioFilePath + "'.");
} else {
//create new AudioStream
audioStream0 = AudioSystem.getAudioInputStream(bufferedInputStream);
}
} catch (IllegalArgumentException e) {
//handle
return false;
} catch (IOException e) {
//handle
return false;
} catch (UnsupportedAudioFileException e) {
//handle
return false;
}
try {
//create new AudioFormat
AudioFormat audioFormat0 = audioStream0.getFormat();
//create new Info
DataLine.Info info0 = new DataLine.Info(Clip.class, audioFormat0);
//initialize new Mixer
Mixer.Info mixerSelected = null;
for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
Mixer mixer = AudioSystem.getMixer(mixerInfo);
if (mixer.isLineSupported(info0)) {
mixerSelected = mixerInfo;
break;
}
}
//create new Clip
Clip audioClip0;
if (mixerSelected != null) {
audioClip0 = AudioSystem.getClip(mixerSelected);
} else {
//THIS EXCEPTION GETS THROWN!!!
throw new IllegalArgumentException("File is not compatible: '" + audioFilePath + "'.");
}
//open created Clips via created AudioStream
audioClip0.open(audioStream0);
//start the play of audio file
audioClip0.start();
//wait until play completed
double waitTime = (double)((((double)audioClip0.getMicrosecondLength()/1000.0)/speed + 50.0) * 0.8);
Thread.sleep((long)waitTime);
return true;
//handle exceptions
} catch (LineUnavailableException e) {
//handle
return false;
} catch (IOException e) {
//handle
return false;
} catch (InterruptedException e) {
//handle
return false;
} catch (IllegalArgumentException e) {
//THIS EXCEPTION GETS THROWN!!!
//handle invalid audio clips
System.out.println(e);
e.printStackTrace();
return false;
}
}
/**
* plays multiple audio files in the order they are stored in an ArrayList
*
* @param fileNames ArrayList<String>: list with filenames of audio files to play
* @param speaker String: speaker to use for playing the audios (can be 'm' or 'w')
* @param speed double: speed applied to the audios
* @return boolean: true if playing audios completed successfully, otherwise false
*/
public static boolean multiFiles(ArrayList<String> fileNames, String speaker, double speed) {
PlayAudio player = new PlayAudio();
//play every audio file in the array of file names
for (int i = 0; (i < fileNames.toArray().length); i ++) {
//generate file names
String fullFileName = speaker + "_" + fileNames.toArray()[i];
//play audio
player.singleFile(fullFileName, speed);
}
return true;
}
}
我已经尝试了什么?
- 我在另一台运行 Ubuntu Linux 的计算机上试过。
- 每次播放新音频时,我都会创建一个
PlayAudio()
的新实例。
- 我在每个音频后使用
audioClip0.stop();
。
- 我将每个音频后的睡眠毫秒数增加到音频长度加 1 秒。
- 我重建了项目……将近 1k 次。
如何重现错误?
我只需要播放超过 62 个音频文件 运行 我的 JAR 文件在 Linux Ubuntu.
你能帮我什么忙?
我不知道如何处理这个问题。用 Linux 播放 .WAV
文件有什么问题?
有解决这个问题的通用方法吗?
(我不允许使用除 OracleJDK 和 OpenJDK 之外的任何库。)
#1 建议来自 Mark Rotteveel。 AudioInputStream
class 需要关闭。这通常会给人们带来惊喜,因为 Java 以管理垃圾收集而闻名。但是对于 AudioInputStream
有资源需要释放。 API 没有充分指出这一点,恕我直言,但是可以从 AudioInputStream.close() 方法的描述中推断出处理的需要:
Closes this audio input stream and releases any system resources
associated with the stream.
#2 建议来自 Andrew Thompson 和 Hendrik 可能比直接解决方案更有帮助,但它仍然是一个非常好的主意,而且我认为所有额外的低效率似乎是合理的,不需要的基础设施(ClassLoader
、InputStream
、BufferedInputStream
)可能是造成此问题的原因。但是我对底层代码的理解真的不够好,无法知道它的相关性。
不过,我认为你可以做得更好。不要使用 Clip
。您当前使用 Clip
违背了其设计理念。 Clips
适用于要保存在内存中并多次播放的持续时间较短的声音,而不是每次播放前反复重新加载的文件。这种使用(加载和播放)的正确 class 是 SourceDataLine
.
可以在 javasound wiki 中找到使用 SourceDataLine
播放的示例。此示例还说明了如何使用 URL
来获取必要的 AudioInputStream
。我会在这里逐字引用它。
public class ExampleSourceDataLine {
public static void main(String[] args) throws Exception {
// Name string uses relative addressing, assumes the resource is
// located in "audio" child folder of folder holding this class.
URL url = ExampleSourceDataLine.class.getResource("audio/371535__robinhood76__06934-distant-ship-horn.wav");
// The wav file named above was obtained from https://freesound.org/people/Robinhood76/sounds/371535/
// and matches the audioFormat.
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
AudioFormat audioFormat = new AudioFormat(
Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
SourceDataLine sourceDataLine = (SourceDataLine)AudioSystem.getLine(info);
sourceDataLine.open(audioFormat);
int bytesRead = 0;
byte[] buffer = new byte[1024];
sourceDataLine.start();
while((bytesRead = audioInputStream.read(buffer)) != -1)
{
// It is possible at this point manipulate the data in buffer[].
// The write operation blocks while the system plays the sound.
sourceDataLine.write(buffer, 0, bytesRead);
}
sourceDataLine.drain();
// release resources
sourceDataLine.close();
audioInputStream.close();
}
}
您必须进行一些编辑,因为示例是通过 main
方法设置为 运行。此外,您将使用您的音频格式,并且音频文件的名称及其文件夹位置必须与您在 getResource()
方法中使用的参数中指定的相对或绝对位置相匹配。此外,buffer
数组的大小可能是首选。 (我经常用8192)
但最重要的是,请注意在此示例中,我们关闭了 SourceDataLine
和 AudioInputStream
。使用 try-with-resources 的替代建议很好,也会释放资源。
如果更改上述内容以适应您的程序有困难,我相信如果您向我们展示您的尝试,我们可以帮助实现它。
应用@Phil Freihofner 的答案后,这对我有用:
/**
* plays an audio file
*
* @param audioFilePath String: path to the audio file
* @param speed double: speed applied to the audios
*/
public boolean singleFile(String audioFilePath) {
//get class
ClassLoader classLoader = getClass().getClassLoader();
//use try-with-resources
//load files from resources folder as stream
try (
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(
new BufferedInputStream(
Objects.requireNonNull(classLoader.getResourceAsStream(audioFilePath))))
) {
if (audioInputStream == null) {
throw new IllegalArgumentException("File not found: '" + audioFilePath + "'.");
}
//create new AudioFormat
AudioFormat audioFormat = audioInputStream.getFormat();
//create new Info
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
//create new SourceDataLine and open it
SourceDataLine sourceDataLine = (SourceDataLine)AudioSystem.getLine(info);
sourceDataLine.open(audioFormat);
//start the play of the audio file
int bytesRead;
byte[] buffer = new byte[8192];
sourceDataLine.start();
while ((bytesRead = audioInputStream.read(buffer)) != -1) {
sourceDataLine.write(buffer, 0, bytesRead);
}
sourceDataLine.drain();
sourceDataLine.close();
audioInputStream.close();
//return true, because play finished
return true;
} catch (Exception e) {
//ignore exceptions
return false;
}
}
感谢大家为我的解决方案做出贡献。
我的 Java 申请需要一些帮助。它的用途是阅读某个网站,所以我需要连续播放很多音频文件。 JAR 是使用 Java 8 编译的。我用 Windows 11 和 Java 16.0.1 测试了我的应用程序,一切正常。然后我使用了最新的 Ubuntu Linux 和 Java 11.0.13 以及 Java 8:它播放了一些音频,但不是每个文件。
我写了一个测试 class,结果是,无论我以何种顺序播放音频,都只播放前(正是!)62 个文件。每个下一个文件(甚至是最初成功播放的文件)都会产生我的代码在这个位置抛出的异常:
if (mixerSelected != null) {
audioClip0 = AudioSystem.getClip(mixerSelected);
} else {
throw new IllegalArgumentException("File is not compatible: '" + audioFilePath + "'.");
}
我确保每个音频文件都是 .WAV
和
- 8k采样率,
- 平均每秒 16k 字节,
- 16 位,并且
- pcm_s16le编解码器。
我的应用程序构建为 JAR 文件,包括资源目录中的音频文件。
这是我的代码:
public class PlayAudio {
/**
* plays an audio file
*
* @param audioFilePath String: path to the audio file
* @param speed double: speed applied to the audios
*/
public boolean singleFile(String audioFilePath, double speed) {
//audioFilePath = "audio" + File.separator + audioFilePath;
audioFilePath = "audio" + "/" + audioFilePath;
AudioInputStream audioStream0;
//create new file using path to the audio
try {
//load files from resources folder as stream
ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream(audioFilePath);
InputStream bufferedInputStream = new BufferedInputStream(inputStream);
if (bufferedInputStream == null) {
throw new IllegalArgumentException("File not found: '" + audioFilePath + "'.");
} else {
//create new AudioStream
audioStream0 = AudioSystem.getAudioInputStream(bufferedInputStream);
}
} catch (IllegalArgumentException e) {
//handle
return false;
} catch (IOException e) {
//handle
return false;
} catch (UnsupportedAudioFileException e) {
//handle
return false;
}
try {
//create new AudioFormat
AudioFormat audioFormat0 = audioStream0.getFormat();
//create new Info
DataLine.Info info0 = new DataLine.Info(Clip.class, audioFormat0);
//initialize new Mixer
Mixer.Info mixerSelected = null;
for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
Mixer mixer = AudioSystem.getMixer(mixerInfo);
if (mixer.isLineSupported(info0)) {
mixerSelected = mixerInfo;
break;
}
}
//create new Clip
Clip audioClip0;
if (mixerSelected != null) {
audioClip0 = AudioSystem.getClip(mixerSelected);
} else {
//THIS EXCEPTION GETS THROWN!!!
throw new IllegalArgumentException("File is not compatible: '" + audioFilePath + "'.");
}
//open created Clips via created AudioStream
audioClip0.open(audioStream0);
//start the play of audio file
audioClip0.start();
//wait until play completed
double waitTime = (double)((((double)audioClip0.getMicrosecondLength()/1000.0)/speed + 50.0) * 0.8);
Thread.sleep((long)waitTime);
return true;
//handle exceptions
} catch (LineUnavailableException e) {
//handle
return false;
} catch (IOException e) {
//handle
return false;
} catch (InterruptedException e) {
//handle
return false;
} catch (IllegalArgumentException e) {
//THIS EXCEPTION GETS THROWN!!!
//handle invalid audio clips
System.out.println(e);
e.printStackTrace();
return false;
}
}
/**
* plays multiple audio files in the order they are stored in an ArrayList
*
* @param fileNames ArrayList<String>: list with filenames of audio files to play
* @param speaker String: speaker to use for playing the audios (can be 'm' or 'w')
* @param speed double: speed applied to the audios
* @return boolean: true if playing audios completed successfully, otherwise false
*/
public static boolean multiFiles(ArrayList<String> fileNames, String speaker, double speed) {
PlayAudio player = new PlayAudio();
//play every audio file in the array of file names
for (int i = 0; (i < fileNames.toArray().length); i ++) {
//generate file names
String fullFileName = speaker + "_" + fileNames.toArray()[i];
//play audio
player.singleFile(fullFileName, speed);
}
return true;
}
}
我已经尝试了什么?
- 我在另一台运行 Ubuntu Linux 的计算机上试过。
- 每次播放新音频时,我都会创建一个
PlayAudio()
的新实例。 - 我在每个音频后使用
audioClip0.stop();
。 - 我将每个音频后的睡眠毫秒数增加到音频长度加 1 秒。
- 我重建了项目……将近 1k 次。
如何重现错误?
我只需要播放超过 62 个音频文件 运行 我的 JAR 文件在 Linux Ubuntu.
你能帮我什么忙?
我不知道如何处理这个问题。用 Linux 播放 .WAV
文件有什么问题?
有解决这个问题的通用方法吗?
(我不允许使用除 OracleJDK 和 OpenJDK 之外的任何库。)
#1 建议来自 Mark Rotteveel。 AudioInputStream
class 需要关闭。这通常会给人们带来惊喜,因为 Java 以管理垃圾收集而闻名。但是对于 AudioInputStream
有资源需要释放。 API 没有充分指出这一点,恕我直言,但是可以从 AudioInputStream.close() 方法的描述中推断出处理的需要:
Closes this audio input stream and releases any system resources associated with the stream.
#2 建议来自 Andrew Thompson 和 Hendrik 可能比直接解决方案更有帮助,但它仍然是一个非常好的主意,而且我认为所有额外的低效率似乎是合理的,不需要的基础设施(ClassLoader
、InputStream
、BufferedInputStream
)可能是造成此问题的原因。但是我对底层代码的理解真的不够好,无法知道它的相关性。
不过,我认为你可以做得更好。不要使用 Clip
。您当前使用 Clip
违背了其设计理念。 Clips
适用于要保存在内存中并多次播放的持续时间较短的声音,而不是每次播放前反复重新加载的文件。这种使用(加载和播放)的正确 class 是 SourceDataLine
.
可以在 javasound wiki 中找到使用 SourceDataLine
播放的示例。此示例还说明了如何使用 URL
来获取必要的 AudioInputStream
。我会在这里逐字引用它。
public class ExampleSourceDataLine {
public static void main(String[] args) throws Exception {
// Name string uses relative addressing, assumes the resource is
// located in "audio" child folder of folder holding this class.
URL url = ExampleSourceDataLine.class.getResource("audio/371535__robinhood76__06934-distant-ship-horn.wav");
// The wav file named above was obtained from https://freesound.org/people/Robinhood76/sounds/371535/
// and matches the audioFormat.
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
AudioFormat audioFormat = new AudioFormat(
Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
SourceDataLine sourceDataLine = (SourceDataLine)AudioSystem.getLine(info);
sourceDataLine.open(audioFormat);
int bytesRead = 0;
byte[] buffer = new byte[1024];
sourceDataLine.start();
while((bytesRead = audioInputStream.read(buffer)) != -1)
{
// It is possible at this point manipulate the data in buffer[].
// The write operation blocks while the system plays the sound.
sourceDataLine.write(buffer, 0, bytesRead);
}
sourceDataLine.drain();
// release resources
sourceDataLine.close();
audioInputStream.close();
}
}
您必须进行一些编辑,因为示例是通过 main
方法设置为 运行。此外,您将使用您的音频格式,并且音频文件的名称及其文件夹位置必须与您在 getResource()
方法中使用的参数中指定的相对或绝对位置相匹配。此外,buffer
数组的大小可能是首选。 (我经常用8192)
但最重要的是,请注意在此示例中,我们关闭了 SourceDataLine
和 AudioInputStream
。使用 try-with-resources 的替代建议很好,也会释放资源。
如果更改上述内容以适应您的程序有困难,我相信如果您向我们展示您的尝试,我们可以帮助实现它。
应用@Phil Freihofner 的答案后,这对我有用:
/**
* plays an audio file
*
* @param audioFilePath String: path to the audio file
* @param speed double: speed applied to the audios
*/
public boolean singleFile(String audioFilePath) {
//get class
ClassLoader classLoader = getClass().getClassLoader();
//use try-with-resources
//load files from resources folder as stream
try (
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(
new BufferedInputStream(
Objects.requireNonNull(classLoader.getResourceAsStream(audioFilePath))))
) {
if (audioInputStream == null) {
throw new IllegalArgumentException("File not found: '" + audioFilePath + "'.");
}
//create new AudioFormat
AudioFormat audioFormat = audioInputStream.getFormat();
//create new Info
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
//create new SourceDataLine and open it
SourceDataLine sourceDataLine = (SourceDataLine)AudioSystem.getLine(info);
sourceDataLine.open(audioFormat);
//start the play of the audio file
int bytesRead;
byte[] buffer = new byte[8192];
sourceDataLine.start();
while ((bytesRead = audioInputStream.read(buffer)) != -1) {
sourceDataLine.write(buffer, 0, bytesRead);
}
sourceDataLine.drain();
sourceDataLine.close();
audioInputStream.close();
//return true, because play finished
return true;
} catch (Exception e) {
//ignore exceptions
return false;
}
}
感谢大家为我的解决方案做出贡献。