我有一个程序,它生成一个短[]音频波形(有符号、大端、44.1KHz 单声道),然后将其写入文件。它不是 .wav 文件,也不是任何其他编解码器...它实际上只是几兆字节的原始 16 位样本。
我有另一个程序正在尝试打开该文件并播放它。以下代码抛出
javax.sound.sampled.UnsupportedAudioFileException: Stream of unsupported format
:
float sampleRate = 44100;
int sampleSizeBits = 16;
int numChannels = 1;
boolean signed = true;
boolean bigEndian = true;
AudioFormat format = new AudioFormat(sampleRate, sampleSizeBits, numChannels, signed, bigEndian);
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(FILE));
// The next line is the one that's throwing the exception
AudioInputStream audioStream = AudioSystem.getAudioInputStream(inputStream);
DataLine.Info info = new DataLine.Info(Clip.class, format);
SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);
sourceDataLine.open(format);
sourceDataLine.start();
byte[] bufferBytes = new byte[4096];
int readBytes = -1;
while ((readBytes = audioStream.read(bufferBytes)) != -1) {
sourceDataLine.write(bufferBytes, 0, readBytes);
}
sourceDataLine.drain();
sourceDataLine.close();
audioStream.close();
根据 tgdavies 的评论,看起来 AudioInputStream 实际上并不能能够从文件中读取音频,该文件实际上是编码原始大端签名 16 位值的字节对序列。当我逐步调用 AudioFormat.getAudioInputStream() 时,它只尝试了以下类型的音频文件读取器:
那么...这是否意味着 Java 没有有一个音频库方法,允许直接从只包含原始签名 16 位样本值(编码为大端字节对)的文件中读取?
是否有其他我可以使用的库,或者我是否必须返回并弄清楚如何将数据写入正确的 .wav 文件之类的文件中?
最后...如果有人碰巧知道...实际上有必要来处理读取文件并将其提供给 sourceDataLine.write() 吗?或者是否有一些不错的方便方法可以让您说,“这是一个文件,它包含{这种格式}的原始音频数据...生成一个线程,读取它,播放它,然后在 {class 上调用 {some method}执行回调}完成后通知我?
我必须克服的第一个问题是弄清楚如何将短[](包含原始签名的16位PCM样本)转换为字节[](按大端顺序)。我想出了这个:
// pcm16 is the short[]
ByteBuffer byteBuffer = ByteBuffer.allocate(pcm16.length * 2);
byteBuffer.order(ByteOrder.BIG_ENDIAN);
ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
shortBuffer.put(pcm16);
byte[] rawBytes = byteBuffer.array();
我不知道这是否是从 Short[] 到 byte[] 的最佳方式...如果有人有任何更好的想法,请发表评论,以便我可以做得更好。一旦获得了 byte[],我就将其写入 .wav 文件,如下所示:
AudioFormat audioFormat = new AudioFormat(48000, 16, 1, true, true);
AudioInputStream ais = new AudioInputStream(new ByteArrayInputStream(rawBytes), audioFormat, rawBytes.length);
File file = new File("tempfile.wav");
try {
ais.reset();
if (file.exists)
file.delete();
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, file);
}
catch (Exception e) {
System.err.println(e.toString());
throw new RuntimeException(e);
}
我遇到的下一个问题是我的原始播放代码开始抛出 java.lang.ClassCastException: com.sun.media.sound.DirectAudioDevice$DirectClip 无法转换为 javax.sound.sampled.SourceDataLine我在另一篇SO帖子中找到了答案:
java.lang.ClassCastException: com.sun.media.sound.DirectAudioDevice$DirectClip无法转换为javax.sound.sampled.SourceDataLine
解决方案或多或少直接来自 pfurbacher 的答案:
public void play(URL url, CountDownLatch outerLatch) {
CountDownLatch syncLatch = new CountDownLatch(1);
System.out.println("playing " + url.getFile());
// Use try-with-resources to ensure the stream and clip are closed.
try (AudioInputStream ais = AudioSystem.getAudioInputStream(url);
Clip clip = AudioSystem.getClip();) {
clip.addLineListener(e -> {
if (e.getType() == LineEvent.Type.STOP) {
syncLatch.countDown();
}
});
clip.open(ais);
clip.start();
syncLatch.await();
while (clip.getFramePosition() < clip.getFrameLength()) {
Thread.yield();
}
} catch (Exception e) {
e.printStackTrace();
}
outerLatch.countDown();
}
private URL getUrl(final File file) {
try {
return file.toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
...我原来的播放代码全部被替换为:
play(getUrl(new File("tempfile.wav")));
注意:上面的代码并不完全是我自己程序中的代码...我对其进行了一些编辑以使其更具可读性,并在此过程中删除了很多确实应该是的错误处理代码在那里 & 将其替换为通用异常的 try/catch 。尽管如此,这还是希望能帮助那些试图弄清楚如何从一小段原始 PCM 数据到实际音频播放的人。