如何使用 Java 播放具有原始大端 16 位有符号值的文件中的音频

问题描述 投票:0回答:1

我有一个程序,它生成一个短[]音频波形(有符号、大端、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() 时,它只尝试了以下类型的音频文件读取器:

  • WaveExtensibleFileReader
  • WaveFloatFileReader
  • WaveFileReader
  • SoftMidiAudioFileReader
  • AuFileReader
  • AiffFileReader

那么...这是否意味着 Java 没有一个音频库方法,允许直接从只包含原始签名 16 位样本值(编码为大端字节对)的文件中读取?

是否有其他我可以使用的库,或者我是否必须返回并弄清楚如何将数据写入正确的 .wav 文件之类的文件中?

最后...如果有人碰巧知道...实际上有必要来处理读取文件并将其提供给 sourceDataLine.write() 吗?或者是否有一些不错的方便方法可以让您说,“这是一个文件,它包含{这种格式}的原始音频数据...生成一个线程,读取它,播放它,然后在 {class 上调用 {some method}执行回调}完成后通知我?

java audio
1个回答
0
投票
感谢 tgdavies 的帮助,我能够通过将短[]编写为正确的 .wav 文件,然后播放它来使其工作(代码如下)。

我必须克服的第一个问题是弄清楚如何将短[](包含原始签名的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 数据到实际音频播放的人。

© www.soinside.com 2019 - 2024. All rights reserved.