Java - 读取,操作和编写WAV文件

yon*_*tan 20 java audio wav

在Java程序中,什么是读取音频文件(最好的办法WAV文件)的数字阵列(float[],short[],...),并写出从数字数组的WAV文件?

Phi*_*ner 10

我通过一个读取WAV文件AudioInputStream.Java Sound Tutorials的以下片段效果很好.

int totalFramesRead = 0;
File fileIn = new File(somePathName);
// somePathName is a pre-existing string whose value was
// based on a user selection.
try {
  AudioInputStream audioInputStream = 
    AudioSystem.getAudioInputStream(fileIn);
  int bytesPerFrame = 
    audioInputStream.getFormat().getFrameSize();
    if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) {
    // some audio formats may have unspecified frame size
    // in that case we may read any amount of bytes
    bytesPerFrame = 1;
  } 
  // Set an arbitrary buffer size of 1024 frames.
  int numBytes = 1024 * bytesPerFrame; 
  byte[] audioBytes = new byte[numBytes];
  try {
    int numBytesRead = 0;
    int numFramesRead = 0;
    // Try to read numBytes bytes from the file.
    while ((numBytesRead = 
      audioInputStream.read(audioBytes)) != -1) {
      // Calculate the number of frames actually read.
      numFramesRead = numBytesRead / bytesPerFrame;
      totalFramesRead += numFramesRead;
      // Here, do something useful with the audio data that's 
      // now in the audioBytes array...
    }
  } catch (Exception ex) { 
    // Handle the error...
  }
} catch (Exception e) {
  // Handle the error...
}
Run Code Online (Sandbox Code Playgroud)

为了编写WAV,我发现这非常棘手.从表面上看,这似乎是一个循环问题,写入的命令依赖于AudioInputStream作为参数.

但是你如何写字节AudioInputStream?不应该有AudioOutputStream

我发现可以定义一个可以访问要实现的原始音频字节数据的对象TargetDataLine.

这需要实现许多方法,但大多数方法可以保持虚拟形式,因为它们不是将数据写入文件所必需的.实施的关键方法是read(byte[] buffer, int bufferoffset, int numberofbytestoread).

由于此方法可能会被多次调用,因此还应该有一个实例变量来指示数据进展的程度,并将其作为上述read方法的一部分进行更新.

实现此方法后,您的对象可用于创建新对象,而新对象又可AudioInputStream用于:

AudioSystem.write(yourAudioInputStream, AudioFileFormat.WAV, yourFileDestination)
Run Code Online (Sandbox Code Playgroud)

作为提醒,AudioInputStream可以使用TargetDataLine作为源创建.

至于直接操作数据,我在上面的代码段示例的最里面的循环中对缓冲区中的数据进行了很好的成功audioBytes.

当您处于该内部循环中时,可以将字节转换为整数或浮点数并乘以一个volume值(范围从0.01.0),然后将它们转换回小端字节.

我相信既然您可以访问该缓冲区中的一系列样本,那么您还可以在该阶段使用各种形式的DSP过滤算法.根据我的经验,我发现最好直接对此缓冲区中的数据进行音量更改,因为这样可以实现最小的增量:每个样本增加一个delta,从而最大限度地减少由于音量引起的不连续性而导致的点击几率.

我发现Java提供的卷的"控制线"倾向于卷的跳转会导致点击,我相信这是因为增量只能在单个缓冲区读取的粒度下实现(通常在一个范围内)每1024个样本更改)而不是将更改分成更小的部分,并将每个样本添加一个.但是我不知道音量控制是如何实现的,所以请大家用这个猜想.

总而言之,Java.Sound一直是一个令人头痛的问题.我错误的教程没有包括直接从字节写文件的明确示例.我错过了教程,在"如何转换..."部分中隐藏了播放文件编码的最佳示例.但是,该教程中有很多有价值的免费信息.


编辑:12/13/17

我已经使用以下代码在我自己的项目中从PCM文件中写入音频.而不是实现TargetDataLine一个可以扩展InputStream并使用它作为AudioInputStream.write方法的参数.

public class StereoPcmInputStream extends InputStream
{
    private float[] dataFrames;
    private int framesCounter;
    private int cursor;
    private int[] pcmOut = new int[2];
    private int[] frameBytes = new int[4];
    private int idx;

    private int framesToRead;

    public void setDataFrames(float[] dataFrames)
    {
        this.dataFrames = dataFrames;
        framesToRead = dataFrames.length / 2;
    }

    @Override
    public int read() throws IOException
    {
        while(available() > 0)
        {
            idx &= 3; 
            if (idx == 0) // set up next frame's worth of data
            {
                framesCounter++; // count elapsing frames

                // scale to 16 bits
                pcmOut[0] = (int)(dataFrames[cursor++] * Short.MAX_VALUE);
                pcmOut[1] = (int)(dataFrames[cursor++] * Short.MAX_VALUE);

                // output as unsigned bytes, in range [0..255]
                frameBytes[0] = (char)pcmOut[0];
                frameBytes[1] = (char)(pcmOut[0] >> 8);
                frameBytes[2] = (char)pcmOut[1];
                frameBytes[3] = (char)(pcmOut[1] >> 8);

            }
            return frameBytes[idx++]; 
        }
        return -1;
    }

    @Override 
    public int available()
    {
        // NOTE: not concurrency safe.
        // 1st half of sum: there are 4 reads available per frame to be read
        // 2nd half of sum: the # of bytes of the current frame that remain to be read
        return 4 * ((framesToRead - 1) - framesCounter) 
                + (4 - (idx % 4));
    }    

    @Override
    public void reset()
    {
        cursor = 0;
        framesCounter = 0;
        idx = 0;
    }

    @Override
    public void close()
    {
        System.out.println(
            "StereoPcmInputStream stopped after reading frames:" 
                + framesCounter);
    }
}
Run Code Online (Sandbox Code Playgroud)

这里要导出的源数据是立体浮点数,范围从-1到1.结果流的格式是16位,立体声,小端.

我省略skipmarkSupported我的具体应用方法.但如果需要的话,添加它们并不困难.


小智 9

这是直接写入 wav 文件的源代码。您只需要了解数学和音响工程即可产生您想要的声音。在这个例子中,方程计算双耳节拍。

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;

public class Program {
    public static void main(String[] args) throws IOException {
        final double sampleRate = 44100.0;
        final double frequency = 440;
        final double frequency2 = 90;
        final double amplitude = 1.0;
        final double seconds = 2.0;
        final double twoPiF = 2 * Math.PI * frequency;
        final double piF = Math.PI * frequency2;

        float[] buffer = new float[(int)(seconds * sampleRate)];

        for (int sample = 0; sample < buffer.length; sample++) {
            double time = sample / sampleRate;
            buffer[sample] = (float)(amplitude * Math.cos(piF * time) * Math.sin(twoPiF * time));
        }

        final byte[] byteBuffer = new byte[buffer.length * 2];

        int bufferIndex = 0;
        for (int i = 0; i < byteBuffer.length; i++) {
            final int x = (int)(buffer[bufferIndex++] * 32767.0);

            byteBuffer[i++] = (byte)x;
            byteBuffer[i] = (byte)(x >>> 8);
        }

        File out = new File("out10.wav");

        final boolean bigEndian = false;
        final boolean signed = true;

        final int bits = 16;
        final int channels = 1;

        AudioFormat format = new AudioFormat((float)sampleRate, bits, channels, signed, bigEndian);
        ByteArrayInputStream bais = new ByteArrayInputStream(byteBuffer);
        AudioInputStream audioInputStream = new AudioInputStream(bais, format, buffer.length);
        AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, out);
        audioInputStream.close();
    }
}
Run Code Online (Sandbox Code Playgroud)


sfu*_*ger 8

关于你想要达到的目标的更多细节会有所帮助.如果原始WAV数据适合您,只需使用FileInputStream和可能的扫描仪将其转换为数字.但是,让我试着给你一些有意义的示例代码来帮助你入门:

为此目的,有一个名为com.sun.media.sound.WaveFileWriter的类.

InputStream in = ...;
OutputStream out = ...;

AudioInputStream in = AudioSystem.getAudioInputStream(in);

WaveFileWriter writer = new WaveFileWriter();
writer.write(in, AudioFileFormat.Type.WAVE, outStream);
Run Code Online (Sandbox Code Playgroud)

您可以实现自己的AudioInputStream,它可以执行任何伏都教操作,将您的数字数组转换为音频数据.

writer.write(new VoodooAudioInputStream(numbers), AudioFileFormat.Type.WAVE, outStream);
Run Code Online (Sandbox Code Playgroud)

正如@stacker所提到的,你当然应该熟悉API.


cay*_*ann 6

如果您需要访问实际样本值,则javax.sound.sample包不适合处理WAV文件.该软件包允许您更改音量,采样率等,但如果您想要其他效果(例如,添加回声),则您可以自己动手.(Java教程暗示应该可以直接处理样本值,但技术作者过度表达.)

这个站点有一个处理WAV文件的简单类:http://www.labbookpages.co.uk/audio/javaWavFiles.html


sta*_*ker 3

javax.sound.sample 包支持 Wave 文件

由于这不是一个简单的 API,因此您应该阅读一篇介绍 API 的文章/教程,例如

Java 声音简介