在Java中交替播放2个不同的频率

时间:2016-11-14 10:36:47

标签: java audio javasound java-api javax.sound.sampled

我是Java Sounds的新手。我想在一个特定时间内循环播放2个不同的频率,每个频率1秒。 就像,如果我有2个频率440hz和16000hz,时间周期是10秒,那么每个'偶数'第二个440hz被播放,每个'奇数'秒16000hz,即每个交替5秒。

我通过一些例子学到了一些东西,并且我还制作了一个程序,该程序在一段时间内由用户在这些示例的帮助下给出单个用户指定的频率。

如果有人可以帮我解决这个问题,我将非常感激。 感谢。

我也附上该单频代码供参考。

  import java.nio.ByteBuffer;
  import java.util.Scanner;
  import javax.sound.sampled.*;

  public class Audio {

   public static void main(String[] args) throws InterruptedException, LineUnavailableException {
    final int SAMPLING_RATE = 44100;            // Audio sampling rate
    final int SAMPLE_SIZE = 2;                  // Audio sample size in bytes

    Scanner in = new Scanner(System.in);
    int time = in.nextInt();                      //Time specified by user in seconds
    SourceDataLine line;
    double fFreq = in.nextInt();                         // Frequency of sine wave in hz

    //Position through the sine wave as a percentage (i.e. 0 to 1 is 0 to 2*PI)
    double fCyclePosition = 0;

    //Open up audio output, using 44100hz sampling rate, 16 bit samples, mono, and big 
    // endian byte ordering
    AudioFormat format = new AudioFormat(SAMPLING_RATE, 16, 1, true, true);
    DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

    if (!AudioSystem.isLineSupported(info)) {
        System.out.println("Line matching " + info + " is not supported.");
        throw new LineUnavailableException();
    }

    line = (SourceDataLine) AudioSystem.getLine(info);
    line.open(format);
    line.start();

    // Make our buffer size match audio system's buffer
    ByteBuffer cBuf = ByteBuffer.allocate(line.getBufferSize());

    int ctSamplesTotal = SAMPLING_RATE * time;         // Output for roughly user specified time in seconds

    //On each pass main loop fills the available free space in the audio buffer
    //Main loop creates audio samples for sine wave, runs until we tell the thread to exit
    //Each sample is spaced 1/SAMPLING_RATE apart in time
    while (ctSamplesTotal > 0) {
        double fCycleInc = fFreq / SAMPLING_RATE;  // Fraction of cycle between samples

        cBuf.clear();                            // Discard samples from previous pass

        // Figure out how many samples we can add
        int ctSamplesThisPass = line.available() / SAMPLE_SIZE;
        for (int i = 0; i < ctSamplesThisPass; i++) {
            cBuf.putShort((short) (Short.MAX_VALUE * Math.sin(2 * Math.PI * fCyclePosition)));

            fCyclePosition += fCycleInc;
            if (fCyclePosition > 1) {
                fCyclePosition -= 1;
            }
        }

        //Write sine samples to the line buffer.  If the audio buffer is full, this will 
        // block until there is room (we never write more samples than buffer will hold)
        line.write(cBuf.array(), 0, cBuf.position());
        ctSamplesTotal -= ctSamplesThisPass;     // Update total number of samples written 

        //Wait until the buffer is at least half empty  before we add more
        while (line.getBufferSize() / 2 < line.available()) {
            Thread.sleep(1);
        }
    }

    //Done playing the whole waveform, now wait until the queued samples finish 
    //playing, then clean up and exit
    line.drain();
    line.close();
}

}

2 个答案:

答案 0 :(得分:2)

您最好的选择可能是创建Clip,如下面的示例代码所示。 也就是说,MHz范围通常是听不见的 - 看起来你的问题中有一个拼写错误。如果不是拼写错误,您将遇到Mr. Nyquist的问题。

另一个提示:没有人在Java中使用Hungarian Notation

import javax.sound.sampled.*;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;

public class AlternatingTones {

    public static void main(final String[] args) throws LineUnavailableException, InterruptedException {

        final Clip clip0 = createOneSecondClip(440f);
        final Clip clip1 = createOneSecondClip(16000f);

        clip0.addLineListener(event -> {
            if (event.getType() == LineEvent.Type.STOP) {
                clip1.setFramePosition(0);
                clip1.start();
            }
        });
        clip1.addLineListener(event -> {
            if (event.getType() == LineEvent.Type.STOP) {
                clip0.setFramePosition(0);
                clip0.start();
            }
        });
        clip0.start();

        // prevent JVM from exiting
        Thread.sleep(10000000);
    }

    private static Clip createOneSecondClip(final float frequency) throws LineUnavailableException {
        final Clip clip = AudioSystem.getClip();
        final AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100f, 16, 1, 2, 44100, true);
        final ByteBuffer buffer = ByteBuffer.allocate(44100 * format.getFrameSize());
        final ShortBuffer shortBuffer = buffer.asShortBuffer();
        final float cycleInc = frequency / format.getFrameRate();
        float cyclePosition = 0f;
        while (shortBuffer.hasRemaining()) {
            shortBuffer.put((short) (Short.MAX_VALUE * Math.sin(2 * Math.PI * cyclePosition)));
            cyclePosition += cycleInc;
            if (cyclePosition > 1) {
                cyclePosition -= 1;
            }
        }
        clip.open(format, buffer.array(), 0, buffer.capacity());
        return clip;
    }
}    

答案 1 :(得分:2)

我将使用的方法是在输出到SourceDataLine时计算帧数。当你写了一秒钟的帧时,切换频率。与试图摆弄剪辑相比,这将提供更好的计时准确性。

我不清楚您所展示的代码是您编写或复制粘贴的内容。如果您对如何运作有疑问,如果您展示自己尝试的内容以及产生的错误或异常,我们很乐意为您提供帮助。

当输出到SourceDataLine时,必须有一个步骤,根据您所拥有的音频格式指定的16位编码,将短值(-32768 .. + 32767)转换为两个字节。我不知道你的代码在哪里完成。 [编辑:可以看到putShort()方法执行此操作的位置,但它仅适用于BigEndian,而不是更常见的LittleEndian。]

您是否查看过Java教程? Sound Trail