分层音频文件时的峰值削波 Java
Peak clipping when layering audio files Java
因此,作为我正在进行的项目的一部分,我尝试将多个音频剪辑相互叠加以创建人群的声音,并将其写入新的 .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 (buffer[0]) 添加到我的缓冲区,这样一切都可以同时播放并且可以正常工作。但是,如果我尝试在整个缓冲区中随机分散各个剪辑,我 运行 就会遇到问题。
当我尝试偏移文件的添加时,相对于缓冲区的长度,我得到了可怕的静态和峰值削波。
buffer[i+offset] += curr[i];
我意识到我需要小心避免溢出,所以我尝试使用整数缓冲区而不是字节缓冲区。
我不明白的是为什么只有在我引入偏移时它才会中断。
我没有 post 实际使用 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()
给你 odd 偏移量,你最终会将一个样本的低字节与另一个样本的高字节混合在一起,这听起来像(通常很糟糕)噪音。也许这就是您认为是削波的声音。
要做到这一点,您需要先解码音频,即考虑样本长度(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 ...
(请注意,您可以轻松地通过批量 decoding/encoding 提高效率,而不是像上面所示那样处理单个样本。我只是觉得它更容易理解。)
在操作过程中,您必须注意样本值。它们不得大于 Short.MAX_VALUE
或小于 Short.MIN_VALUE
。如果您检测到您超出了有效范围,只需缩放 整个数组 。这样你就可以避免剪裁。
祝你好运!
因此,作为我正在进行的项目的一部分,我尝试将多个音频剪辑相互叠加以创建人群的声音,并将其写入新的 .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 (buffer[0]) 添加到我的缓冲区,这样一切都可以同时播放并且可以正常工作。但是,如果我尝试在整个缓冲区中随机分散各个剪辑,我 运行 就会遇到问题。
当我尝试偏移文件的添加时,相对于缓冲区的长度,我得到了可怕的静态和峰值削波。
buffer[i+offset] += curr[i];
我意识到我需要小心避免溢出,所以我尝试使用整数缓冲区而不是字节缓冲区。
我不明白的是为什么只有在我引入偏移时它才会中断。
我没有 post 实际使用 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()
给你 odd 偏移量,你最终会将一个样本的低字节与另一个样本的高字节混合在一起,这听起来像(通常很糟糕)噪音。也许这就是您认为是削波的声音。
要做到这一点,您需要先解码音频,即考虑样本长度(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 ...
(请注意,您可以轻松地通过批量 decoding/encoding 提高效率,而不是像上面所示那样处理单个样本。我只是觉得它更容易理解。)
在操作过程中,您必须注意样本值。它们不得大于 Short.MAX_VALUE
或小于 Short.MIN_VALUE
。如果您检测到您超出了有效范围,只需缩放 整个数组 。这样你就可以避免剪裁。
祝你好运!