You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

原始16位音频数据播放异常问题

我有一个程序会生成short[]类型的音频波形(有符号、大端、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));

// 抛出异常的代码行
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();

更新内容

调试发现AudioInputStream仅尝试以下音频文件阅读器,无法读取仅包含大端16位有符号值字节对的原始文件:

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

想请教几个问题:

  1. Java是否没有可直接读取此类原始音频文件的库方法?
  2. 是否有其他可用库,还是必须将数据写入标准.wav文件?
  3. 是否必须自行处理文件读取并调用sourceDataLine.write()?有没有便捷方法可指定格式后自动播放并回调?

解决方案

核心问题原因

AudioSystem.getAudioInputStream(inputStream)是用于读取带文件头的标准音频格式文件(如WAV、AIFF)的方法,你的文件是无格式头的原始采样数据,因此该方法无法识别,直接抛出异常。Java标准库确实没有内置直接读取原始音频数据生成AudioInputStream的方法,它默认只处理带格式封装的音频文件。

解决方法选项

选项1:手动读取原始数据并写入SourceDataLine

这是最直接的方案,无需额外库,直接处理文件读取并写入数据行即可(你的数据格式与指定的AudioFormat一致,无需额外转换)。修改后的代码示例:

float sampleRate = 44100;
int sampleSizeBits = 16;
int numChannels = 1;
boolean signed = true;
boolean bigEndian = true;
AudioFormat format = new AudioFormat(sampleRate, sampleSizeBits, numChannels, signed, bigEndian);

// 直接读取原始文件流,无需AudioInputStream
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(FILE));

// 修正DataLine.Info的目标类型为SourceDataLine
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);
sourceDataLine.open(format);
sourceDataLine.start();

byte[] bufferBytes = new byte[4096];
int readBytes = -1;
while ((readBytes = inputStream.read(bufferBytes)) != -1) {
    sourceDataLine.write(bufferBytes, 0, readBytes);
}

sourceDataLine.drain();
sourceDataLine.close();
inputStream.close();

如果需要回调逻辑,可以封装一个播放线程实现:

public class RawAudioPlayer {
    public interface PlaybackCallback {
        void onPlaybackComplete();
        void onPlaybackError(Exception e);
    }

    public static void playRawAudio(File file, AudioFormat format, PlaybackCallback callback) {
        new Thread(() -> {
            try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
                 SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, format))) {

                sourceDataLine.open(format);
                sourceDataLine.start();

                byte[] buffer = new byte[4096];
                int read;
                while ((read = inputStream.read(buffer)) != -1) {
                    sourceDataLine.write(buffer, 0, read);
                }

                sourceDataLine.drain();
                sourceDataLine.close();
                callback.onPlaybackComplete();
            } catch (Exception e) {
                callback.onPlaybackError(e);
            }
        }).start();
    }
}

使用示例:

AudioFormat format = new AudioFormat(44100, 16, 1, true, true);
RawAudioPlayer.playRawAudio(new File(FILE), format, new RawAudioPlayer.PlaybackCallback() {
    @Override
    public void onPlaybackComplete() {
        System.out.println("播放完成");
    }

    @Override
    public void onPlaybackError(Exception e) {
        e.printStackTrace();
    }
});

选项2:将原始数据封装为WAV格式再播放

如果需要兼容AudioInputStream的API,可以临时将原始数据包装成WAV格式的字节流(内存或临时文件),WAV文件头结构固定,可手动构造:

public static AudioInputStream wrapRawToWav(InputStream rawStream, AudioFormat format) throws IOException {
    long frameLength = rawStream.available() / format.getFrameSize();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);

    // 构造RIFF块
    dos.writeBytes("RIFF");
    dos.writeInt((int)(36 + frameLength * format.getFrameSize()));
    dos.writeBytes("WAVE");

    // 构造fmt块(PCM格式)
    dos.writeBytes("fmt ");
    dos.writeInt(16);
    dos.writeShort(1);
    dos.writeShort(format.getChannels());
    dos.writeInt((int)format.getSampleRate());
    dos.writeInt((int)(format.getSampleRate() * format.getFrameSize()));
    dos.writeShort(format.getFrameSize());
    dos.writeShort(format.getSampleSizeInBits());

    // 构造data块
    dos.writeBytes("data");
    dos.writeInt((int)(frameLength * format.getFrameSize()));

    // 写入原始音频数据
    byte[] buffer = new byte[4096];
    int read;
    while ((read = rawStream.read(buffer)) != -1) {
        dos.write(buffer, 0, read);
    }
    dos.close();

    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    return AudioSystem.getAudioInputStream(bais);
}

包装后即可用你原本的代码通过AudioInputStream播放。

选项3:使用第三方库

如果不想自行实现,可以使用TarsosDSP等第三方音频库,这类库支持直接读取原始音频数据并播放。不过多数场景下,前两个选项已足够解决问题,无需额外引入依赖。

总结

  • Java标准库没有直接读取无格式头原始音频的AudioInputStream实现,需自行处理或封装为标准格式。
  • 优先选择手动读取原始数据写入SourceDataLine的方案,简单高效且无额外依赖。
  • 若需要回调,可自行封装播放线程;若要兼容AudioInputStreamAPI,可临时封装为WAV格式。

内容的提问来源于stack exchange,提问作者Bitbang3r

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 06:11:22