为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕田峪9158850 2022-07-20 10:33:40
因此,作为我正在进行的项目的一部分,我试图将多个音频片段叠加在一起以创建人群的声音,并将其写入新的 .WAV 文件。首先,我创建一个文件(一个 16 位 PCM .WAV 文件)的 byte[] 表示,这似乎不会导致任何问题。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(缓冲区 [0])添加到我的缓冲区,因此所有内容都可以立即播放,并且可以正常工作。但是,如果我尝试在整个缓冲区中随机分散单个剪辑,我会遇到问题。当我尝试抵消文件的添加时,相对于缓冲区的长度,我得到了可怕的静态和削峰。 buffer[i+offset] += curr[i];我意识到我需要小心避免溢出,这就是为什么我尝试使用整数缓冲区而不是字节缓冲区。我不明白的是为什么它只在我引入偏移时才会中断。我没有发布实际使用 AudioSystem 对象创建新文件的代码,因为它似乎没有任何效果。这是我第一次使用音频编程,因此非常感谢任何帮助。
查看完整描述

1 回答

?
牧羊人nacy

TA贡献1862条经验 获得超7个赞

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


要做到这一点,您需要先解码音频,即考虑样本长度(2 字节)和通道数(?),进行操作,然后将音频再次编码为字节流。


假设您只有一个通道并且字节顺序是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。如果您检测到您超出了有效范围,只需缩放整个数组。这样你就可以避免剪裁。


查看完整回答
反对 回复 2022-07-20
  • 1 回答
  • 0 关注
  • 73 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信