使用javax.sound.sampled生成声音

时间:2011-02-03 17:55:11

标签: java audio

我正在尝试使用Java生成声音。最后,我愿意不断向声卡发送声音,但是现在我可以发送一个独特的声波。

所以,我在一个阵列中填充了44100个符号整数,表示一个简单的正弦波,我想将它发送到我的声卡,但我无法让它工作。

int samples = 44100; // 44100 samples/s
int[] data = new int[samples];

// Generate all samples
for ( int i=0; i<samples; ++i )
{
  data[i] = (int) (Math.sin((double)i/(double)samples*2*Math.PI)*(Integer.MAX_VALUE/2));
}

我使用以下方法将其发送到声音线:

AudioFormat format = new AudioFormat(Encoding.PCM_SIGNED, 44100, 16, 1, 1, 44100, false);

Clip clip = AudioSystem.getClip();
AudioInputStream inputStream = new AudioInputStream(ais,format,44100);
clip.open(inputStream);
clip.start(); 

我的问题存在于代码片段之间。我找不到将int[]转换为输入流的方法!

2 个答案:

答案 0 :(得分:5)

首先,我认为您需要short个样本而不是int

short[] data = new short[samples];

因为您的AudioFormat指定了16位样本。 short为16位宽,int为32位。

将其转换为流的简便方法是:

  • 分配ByteBuffer
  • 使用putShort来电
  • 填充它
  • 将结果byte[]换成ByteArrayInputStream
  • AudioInputStream创建ByteArrayInputStream并格式化

示例:

float frameRate = 44100f; // 44100 samples/s
int channels = 2;
double duration = 1.0;
int sampleBytes = Short.SIZE / 8;
int frameBytes = sampleBytes * channels;
AudioFormat format =
    new AudioFormat(Encoding.PCM_SIGNED,
                    frameRate,
                    Short.SIZE,
                    channels,
                    frameBytes,
                    frameRate,
                    true);
int nFrames = (int) Math.ceil(frameRate * duration);
int nSamples = nFrames * channels;
int nBytes = nSamples * sampleBytes;
ByteBuffer data = ByteBuffer.allocate(nBytes);
double freq = 440.0;
// Generate all samples
for ( int i=0; i<nFrames; ++i )
{
  double value = Math.sin((double)i/(double)frameRate*freq*2*Math.PI)*(Short.MAX_VALUE);
  for (int c=0; c<channels; ++ c) {
      int index = (i*channels+c)*sampleBytes;
      data.putShort(index, (short) value);
  }
}

AudioInputStream stream =
    new AudioInputStream(new ByteArrayInputStream(data.array()), format, nFrames*2);
Clip clip = AudioSystem.getClip();
clip.open(stream);
clip.start();
clip.drain();

注意:我已将AudioFormat更改为立体声,因为当我请求单声道线时它会引发异常。我还将波形的频率提高到可听范围内。


更新 - 之前的修改(直接写入数据行)不是必需的 - 使用Clip工作正常。我还介绍了一些变量,使计算更加清晰。

答案 1 :(得分:0)

如果您想播放简单的声音,则应使用SourceDataLine。

以下是一个例子:

import javax.sound.sampled.*;
public class Sound implements Runnable {

  //Specify the Format as
  //44100 samples per second (sample rate)
  //16-bit samples,
  //Mono sound,
  //Signed values,
  //Big-Endian byte order
  final AudioFormat format=new AudioFormat(44100f,16,2,true,true);

  //Your output line that sends the audio to the speakers
  SourceDataLine line;

  public Sound(){
    try{
      line=AudioSystem.getSourceDataLine(format);
      line.open(format);
    }catch(LineUnavailableExcecption oops){
      oops.printStackTrace();
    }
    new Thread(this).start();
  }

  public void run(){
    //a buffer to store the audio samples
    byte[] buffer=new byte[1000];
    int bufferposition=0;

    //a counter to generate the samples
    long c=0;

    //The pitch of your sine wave (440.0 Hz in this case)
    double wavelength=44100.0/440.0;

    while(true){
      //Generate a sample
      short sample=(short) (Math.sin(2*Math.PI*c/wavelength)*32000);

      //Split the sample into two bytes and store them in the buffer
      buffer[bufferposition]=(byte) (sample>>>8);
      bufferposition++;
      buffer[bufferposition]=(byte) (sample & 0xff);
      bufferposition++;

      //if the buffer is full, send it to the speakers
      if(bufferposition>=buffer.length){
        line.write(buffer,0,buffer.length);
        line.start();
        //Reset the buffer
        bufferposition=0;
      }
    }
    //Increment the counter
    c++;
  }

  public static void main(String[] args){
    new Sound();
  }
}

在此示例中,您将继续生成正弦波,但您可以使用此代码从您想要的任何来源播放声音。您只需要确保正确格式化样本。在这种情况下,我使用原始的,未压缩的16位采样,采样率为44100 Hz。但是,如果要播放文件中的音频,可以使用Clip对象

public void play(File file){
  Clip clip=AudioSystem.getClip();
  clip.open(AudioSystem.getAudioInputStream(file));
  clip.loop(1);
}