【问题标题】:Java sound generation produces noisy soundJava 声音生成产生嘈杂的声音
【发布时间】:2014-10-18 06:32:39
【问题描述】:

我正在使用 javax.sound 来发出声音,但是当您播放它时,它们会在背景中产生某种噪音,如果您同时演奏几个音符,甚至会克服声音。代码如下:

public final static double notes[] = new double[] {130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185,
        196, 207.65, 220, 233.08, 246.94, 261.63, 277.18, 293.66,
        311.13, 329.63, 349.23, 369.99, 392, 415.3, 440, 466.16,
        493.88, 523.25, 554.37};



public static void playSound(int note, int type) throws LineUnavailableException {          //type 0 = sin, type 1 = square

    Thread t = new Thread() {
        public void run() {
            try {

                int sound = (int) (notes[note] * 100);


                byte[] buf = new byte[1];
                AudioFormat af = new AudioFormat((float) sound, 8, 1, true,
                        false);
                SourceDataLine sdl;

                sdl = AudioSystem.getSourceDataLine(af);

                sdl = AudioSystem.getSourceDataLine(af);
                sdl.open(af);
                sdl.start();
                int maxi = (int) (1000 * (float) sound / 1000);
                for (int i = 0; i < maxi; i++) {
                    double angle = i / ((float) 44100 / 440) * 2.0
                            * Math.PI;
                    double val = 0;
                    if (type == 0) val = Math.sin(angle)*100;
                    if (type == 1) val = square(angle)*50;

                    buf[0] = (byte) (val * (maxi - i) / maxi);
                    sdl.write(buf, 0, 1);
                }
                sdl.drain();
                sdl.stop();
                sdl.close();
            } catch (LineUnavailableException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        };
    };

    t.start();
}

public static double square (double angle){
    angle = angle % (Math.PI*2);
    if (angle > Math.PI) return 1;
    else return 0;
}

此代码来自这里:https://stackoverflow.com/a/1932537/3787777

【问题讨论】:

  • 可能数据的速率与您在解码时使用的速率不同。试试 22000 或类似的东西。
  • 实际上你的代码是为一个音符工作的,如果你演奏几个音符,那么你需要一个流,而不是每个音符一个线程
  • 我写了multi-tone generator during hackathon。它适用于 Android,但原理是相同的。你可能会在那里找到一些有用的东西。我的建议是预先生成主正弦波,然后以不同的速度迭代它。通过这种方法,您可以将不需要的音频故障降至最低。

标签: java midi


【解决方案1】:

在这个答案中,我将参考 1)您的代码,2)更好的方法(恕我直言:)和 3)同时演奏两个音符。

您的代码

首先,采样率应该取决于音符频率。因此尝试:

AudioFormat(44100,...

接下来,使用16 位采样(听起来更好!)。这是您的代码,可以播放简单的音调而没有噪音 - 但我会以不同的方式使用它(见下文)。请寻找cmets:

Thread t = new Thread() {
    public void run() {
        try {

            int sound = (440 * 100);        // play A
            AudioFormat af = new AudioFormat(44100, 16, 1, true, false);
            SourceDataLine sdl;
            sdl = AudioSystem.getSourceDataLine(af);
            sdl.open(af, 4096 * 2);
            sdl.start();

            int maxi = (int) (1000 * (float) sound / 1000); // should not depend on notes frequency!
            byte[] buf = new byte[maxi * 2];   // try to find better len!
            int i = 0;
            while (i < maxi * 2) {
                // formula is changed to be simple sine!!
                double val = Math.sin(Math.PI * i * 440 / 44100);
                short s = (short) (Short.MAX_VALUE * val);
                buf[i++] = (byte) s;
                buf[i++] = (byte) (s >> 8);   // little endian
            }
            sdl.write(buf, 0, maxi);
            sdl.drain();
            sdl.stop();
            sdl.close();
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
    }
};

t.start();

更好的代码建议

这是您的代码的简化版本,它播放一些音符(频率) 没有噪音。我更喜欢它,因为我们首先创建了 doubles 数组,它们是通用值。这些值可以组合在一起,或者存储或进一步修改。然后我们将它们转换为(8 位或 16 位)样本值。

private static byte[] buffer = new byte[4096 * 2 / 3];
private static int bufferSize = 0;

// plays a sample in range (-1, +1).
public static void play(SourceDataLine line, double in) {
    if (in < -1.0) in = -1.0;  // just sanity checks
    if (in > +1.0) in = +1.0;

    // convert to bytes - need 2 bytes for 16 bit sample
    short s = (short) (Short.MAX_VALUE * in);
    buffer[bufferSize++] = (byte) s;
    buffer[bufferSize++] = (byte) (s >> 8);   // little Endian

    // send to line when buffer is full
    if (bufferSize >= buffer.length) {
        line.write(buffer, 0, buffer.length);
        bufferSize = 0;
    }
    // todo: be sure that whole buffer is sent to line!
}

// prepares array of doubles, not related with the sampling value!
private static double[] tone(double hz, double duration) {
    double amplitude = 1.0;
    int N = (int) (44100 * duration);
    double[] a = new double[N + 1];
    for (int i = 0; i <= N; i++) {
        a[i] = amplitude * Math.sin(2 * Math.PI * i * hz / 44100);
    }
    return a;
}

// finally:
public static void main(String[] args) throws LineUnavailableException {
    AudioFormat af = new AudioFormat(44100, 16, 1, true, false);
    SourceDataLine sdl = AudioSystem.getSourceDataLine(af);

    sdl.open(af, 4096 * 2);
    sdl.start();

    double[] tones = tone(440, 2.0);    // play A for 2 seconds
    for (double t : tones) {
        play(sdl, t);
    }

    sdl.drain();
    sdl.stop();
    sdl.close();
}

听起来不错;)

同时弹奏两个音符

只需合并两个音符:

double[] a = tone(440, 1.0);        // note A
double[] b = tone(523.25, 1.0);     // note C (i hope:)
for (int i = 0; i < a.length; i++) {
    a[i] = (a[i] + b[i]) / 2;
}
for (double t : a) {
    play(sdl, t);
}

请记住,使用双阵列,您可以组合和操纵您的音调 - 即合成同时播放的音调。当然,如果加上3个色调,则需要通过除以3等方式将值归一化。

叮咚:)

【讨论】:

  • 听起来不错,但是如何同时播放 2 个声音?我尝试用double[] tones = tone(440, 2.0); // play A for 2 seconds double[] tones2 = tone (500, 2.0); for (int t = 0; t &lt; tones.length; t++) { play(sdl, tones[t]); play(sdl, tones2[t]); } 替换double[] tones = tone(440, 2.0); // play A for 2 seconds for (double t : tones) { play(sdl, t); },但它不能正常工作。
  • 一种方法是将两个正弦相加并除以二(我更新了示例)。当然,对于更多的音符,您需要除以同时演奏的音符数量。最后,应该有一种方法可以通过写入源代码来播放,但目前有些东西对我不起作用:(无论如何我不会使用线程。
  • 这太棒了:我只是试试这个很有趣,你能给我一个音调列表和每个人的时间段吗?
  • 谢谢!这正是我想要的。
【解决方案2】:

答案已经给出,但我想提供一些可能有助于理解解决方案的信息。

为什么是 44100?

44.1 kHz 音频被广泛使用,因为这是 CD 中使用的采样率。模拟音频是通过每秒采样 44,100 次(每秒 1 个周期 = 1 Hz)来记录的,然后这些样本在播放时用于重建音频信号。选择这个频率的原因相当复杂;对于这个解释并不重要。也就是说,使用 22000 的建议不是很好,因为该频率太接近人类的听觉范围(20Hz - 20kHz)。您可能希望使用高于 40kHz 的采样率来获得良好的音质。我认为 mp4 使用 96kHz。

为什么是 16 位?

用于 CD 的标准是 44.1kHz/16 位。 MP4 使用 96kHz/24 位。采样率是指每秒记录多少个 X 位样本。 CD 质量采样使用 44,100 个 16 位样本来再现声音。

为什么这个解释很重要?

要记住的是,您正在尝试制作数字声音(不是模拟声音)。这意味着这些位和字节必须由音频 CODEC 处理。在硬件中,音频编解码器是将模拟音频编码为数字信号并将数字解码回模拟信号的设备。对于音频输出,数字化声音必须通过数模转换器 (DAC) 才能从扬声器中发出正确的声音。 DAC 的两个最重要的特性是其带宽和信噪比,而 DAC 的实际带宽主要由其采样率来表征

基本上,您不能使用任意采样率,因为上述原因您的音频设备无法很好地再现音频。如有疑问,请检查您的计算机硬件并了解您的 CODEC 支持什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多