在Java中生成正弦波时有噪声问题

5
我运行以下代码时,背景中会出现轻微扭曲声(听起来像嗡嗡声)。由于其微妙的性质,使我相信在字节转换时存在某种赋值现象。音频格式为 PCM_SIGNED 44100.0 Hz,16位,立体声,每帧4个字节,大端序。请注意:目前代码假设数据为大端序。
public static void playFreq(AudioFormat audioFormat, double frequency, SourceDataLine sourceDataLine)
{
    System.out.println(audioFormat);
    double sampleRate = audioFormat.getSampleRate();
    int sampleSizeInBytes = audioFormat.getSampleSizeInBits() / 8;
    int channels = audioFormat.getChannels();

    byte audioBuffer[] = new byte[(int)Math.pow(2.0, 19.0) * channels * sampleSizeInBytes];

    for ( int i = 0; i < audioBuffer.length; i+=sampleSizeInBytes*channels )
    {
        int wave = (int) (127.0 * Math.sin( 2.0 * Math.PI * frequency * i / (sampleRate * sampleSizeInBytes * channels) )  );

        //wave = (wave > 0 ? 127 : -127);

        if ( channels == 1 )
        {
            if ( sampleSizeInBytes == 1 )
            {
                audioBuffer[i] = (byte) (wave);
            }

            else if ( sampleSizeInBytes == 2 )
            {
                audioBuffer[i] = (byte) (wave);
                audioBuffer[i+1] = (byte)(wave >>> 8);
            }
        }

        else if ( channels == 2 )
        {
            if ( sampleSizeInBytes == 1 )
            {
                audioBuffer[i] = (byte) (wave);
                audioBuffer[i+1] = (byte) (wave);
            }

            else if ( sampleSizeInBytes == 2 )
            {
                audioBuffer[i] = (byte) (wave);
                audioBuffer[i+1] = (byte)(wave >>> 8);

                audioBuffer[i+2] = (byte) (wave);
                audioBuffer[i+3] = (byte)(wave >>> 8);
            }
        }
    }

    sourceDataLine.write(audioBuffer, 0, audioBuffer.length);
}
2个回答

7

您的评论指出代码假定是大端字节序。

严格来说,实际上您输出的是小端字节序。然而,由于一些幸运的巧合,最高有效字节总是为0,所以这似乎无所谓。

编辑:进一步解释一下——当您的值达到最大值127时,您应该写入(0x00,0x7f),但实际输出是(0x7f,0x00),即32512。这恰好接近16位最大值32767,但底部8位全为零。最好始终使用32767作为最大值,然后在需要时丢弃底部8位。

这意味着,即使您输出16位数据,有效分辨率也只有8位。这似乎可以解释声音质量不足的原因。

我制作了一个版本的您的代码,只是将原始数据转储到文件中,并且没有发现位移本身存在任何意外的更改、缺失位等问题,但是存在8位采样质量一致的杂音。

另外,如果您根据样本计数计算波动方程,然后单独考虑字节偏移,那么您的数学运算会更容易:

int samples = 2 << 19;
byte audioBuffer[] = new byte[samples * channels * sampleSizeInBytes];

for ( int i = 0, j = 0; i < samples; ++i )
{
    int wave = (int)(32767.0 * Math.sin(2.0 * Math.PI * frequency * i / sampleRate));
    byte msb = (byte)(wave >>> 8);
    byte lsb = (byte) wave;

    for (int c = 0; c < channels; ++c) {
        audioBuffer[j++] = msb;
        if (sampleSizeInBytes > 1) {
            audioBuffer[j++] = lsb;
        }
    }
 }

啊!现在看到错误了,在修复振幅部分后,错误变得非常明显,但你的代码也更有效率。谢谢! - worbel

2
我猜想您正在重复调用此代码以播放长声音。
有没有可能您生成的波形在被写入前没有完成一个完整的周期?
如果波形在完成一个完整的周期之前被“截断”,然后下一个波形被写入输出,那么您肯定会听到一些奇怪的声音,我认为这可能是导致嗡嗡声的原因。
例如:
        /-------\              /-------\              /-------\
  -----/         \       -----/         \       -----/         \
                  \                      \                      \
                   \-----                 \-----                 \-----

注意这个波的不连贯性。这可能会导致嗡嗡声。


是的,这不是问题,因为缓冲区足够大,可以通过许多周期传递声音。它解释了点击声,但没有解释持续的嗡嗡声。 - worbel
这不是正确的答案 - 代码明显生成了大量样本,并且每个周期末没有截断。 - Alnitak
我已经测试了代码以进行检查 - 这是一个无意的8位量化误差导致了嗡嗡声。 - Alnitak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接