java - Java 分层音频文件时的峰值削波

标签 java audio signal-processing javasound

因此,作为我正在进行的项目的一部分,我尝试将多个音频剪辑相互叠加以创建人群的声音,并将其写入新的 .WAV 文件。

首先,我创建一个文件的 byte[] 表示形式(16 位 PCM .WAV 文件),这似乎不会导致任何问题。

public byte[] toByteArray(File file)
{
    try
    {
        AudioInputStream in = AudioSystem.getAudioInputStream(file);

        byte[] byteArray = new byte[(int) file.length()];//make sure the size is correct

        while (in.read(byteArray) != -1) ;//read in byte by byte until end of audio input stream reached

        return byteArray;//return the new byte array
    }

然后,我创建一个缓冲区(一个整数数组,以便在添加字节时防止字节溢出)并尝试在文件的字节数组版本中分层。

 int[] buffer = new int[bufferLength];//buffer of appropriate length
        int offset = 0;//no offset for the very first file

        while(!convertedFiles.isEmpty())//until every sample has been added
        {
            byte[] curr = convertedFiles.pop();//get a sample from list

            if(curr.length+offset < bufferLength)
            {
                for (int i =0; i < curr.length; i++)
                {
                    buffer[i] += curr[i];
                }
            }

           offset = randomiseOffset();//next sample placed in a random location in the buffer
        }

当我尝试实现某种随机偏移时,问题就出现了。 我可以将所有音频从索引 0 (buffer[0]) 添加到缓冲区,因此所有内容都会立即播放并且可以正常工作。但是,如果我尝试将各个剪辑随机分散在整个缓冲区中,我就会遇到问题。

当我尝试偏移文件的添加时,相对于缓冲区的长度,我得到了可怕的静态和峰值剪切。

 buffer[i+offset] += curr[i];

我意识到我需要小心避免溢出,所以这就是为什么我尝试使用整数缓冲区而不是字节缓冲区。

但我不明白的是为什么它只有在我引入偏移时才会中断。

我没有发布实际使用 AudioSystem 对象创建新文件的代码,因为它似乎没有任何效果。

这是我第一次使用音频编程,因此非常感谢任何帮助。

编辑:

Hendrik 的回答解决了我的问题,但我只需要稍微更改建议的代码(一些类型转换问题):

    private static short byteToShortLittleEndian(final byte[] buf, final int offset)
{
    int sample = (buf[offset] & 0xff) + ((buf[offset+1] & 0xff) << 8);
    return (short)sample;
}

private static byte[] shortToByteLittleEndian(final short[] samples, final int offset)
{
    byte[] buf = new byte[2];
    int sample = samples[offset];
    buf[0] = (byte) (sample & 0xFF);
    buf[1] = (byte) ((sample >> 8) & 0xFF);
    return buf;
}

最佳答案

您的 randomiseOffset() 方法是什么样的?是否考虑到每个音频样本有两个字节长?如果randomiseOffset()给你奇数偏移量,你最终会将一个样本的低字节与另一个样本的高字节混合,这听起来像(通常是可怕的)噪音。也许这就是您认为是削波的声音。

要正确执行此操作,您需要首先解码音频,即考虑样本长度(2 个字节)和 channel 数(?),进行操作,然后再次将音频编码为字节流。

假设您只有一个 channel ,字节顺序为 little-endian 。然后,您将两个字节解码为示例值,如下所示:

private static int byteToShortLittleEndian(final byte[] buf, final int offset) {
    int sample = (buf[offset] & 0xff) + ((buf[offset+1] & 0xff) << 8);
    return (short)sample;
}

编码,您可以使用如下内容:

private static byte[] shortToByteLittleEndian(final int[] samples, final int offset) {
    byte[] buf = new byte[2];
    int sample = samples[offset];
    buf[0] = sample & 0xFF;
    buf[1] = (sample >> 8) & 0xFF;
    return buf;
}

以下是这两种方法在您的案例中的使用方式:

byte[] byteArray = ...;  // your array
// DECODE: convert to sample values
int[] samples = byteArray.length / 2;
for (int i=0; i<samples.length; i++) {
    samples[i] = byteToShortLittleEndian(byteArray, i*2);
}
// now do your manipulation on the samples array
[...]
// ENCODE: convert back to byte values
byte[] byteOut = new byte[byteArray.length];
for (int i=0; i<samples.length; i++) {
    byte[] b = shortToByteLittleEndian(samples, i);
    byteOut[2*i] = b[0];
    byteOut[2*i+1] = b[1];
}
// do something with byteOut ...

(请注意,您可以通过批量解码/编码轻松提高效率,而不是像上面所示的那样处理单个样本。我只是认为它更容易理解。)

在操作过程中,您必须注意样本值。它们不得大于 Short.MAX_VALUE 或小于 Short.MIN_VALUE。如果您发现超出了有效范围,只需缩放整个数组即可。这样就可以避免剪辑。

祝你好运!

关于java - Java 分层音频文件时的峰值削波,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54974924/

相关文章:

algorithm - 发现信号中的周期性模式

python - 在 Python 中使用 LPC 估计共振峰

java - 如何检索 Swing 表单值

java - 如何在android Canvas 中使用Double Tap事件来保存当前图像

java - 在两个数组中查找重复值

c# - 添加/删除声音设备后,如何在NAudio中选择正确的声音输出设备?

java - 无法访问 Web 项目上 jar 文件中的类

java - 如何获得 AudioInputStream 的持续时间?

java - 在特定采样/时间循环音频

matlab - 使用 MATLAB 对信号进行上采样