2011-02-03 68 views
1

我试图用Java生成声音。最后,我愿意不断地向声卡发送声音,但现在我可以发送一个独特的声波。使用javax.sound.sampled生成声音

所以,我用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[]转换为输入流!

回答

5

首先,我想你想short样品,而不是int

short[] data = new short[samples]; 

因为你AudioFormat指定16位采样。 short是16位宽,但是int是32位。

一个简单的方法将其转换为一个流:

  • 分配一个ByteBuffer
  • 使用putShort电话
  • 填充它
  • 裹在ByteArrayInputStream
  • 产生的byte[]创建从一个AudioInputStreamByteArrayInputStream和格式

例子:

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工作正常。我还介绍了一些变量,以使计算更清晰。

+0

您真棒。保存了我的一天:) – NewbiZ 2011-02-04 01:05:06

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(); 
    } 
} 

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

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