傅立叶变换字节数组
Fourier transforming a byte array
我对Java不是很精通,所以请保持简单。不过,我会尝试理解您 post 的一切。这是我的问题。
我已经编写了代码来从外部麦克风录制音频并将其存储在 .wav 中。存储此文件与存档目的相关。我需要做的是对存储的音频进行 FFT。
我的方法是将 wav 文件加载为字节数组并将其转换,问题是 1. 我需要摆脱的方式有一个 header,但我应该能够做到这一点和 2. 我得到了一个字节数组,但是我在网上找到的大多数(如果不是全部)FFT 算法都试图修补到我的项目中,使用复杂的/两个双精度数组。
我尝试解决这两个问题并最终能够将我的 FFT 阵列绘制成图表,但我发现它只是返回“0”。 .wav 文件很好,我可以毫无问题地播放它。我想也许将字节转换成双精度对我来说是个问题,所以这是我的方法(我知道这不漂亮)
byte ByteArray[] = Files.readAllBytes(wav_path);
String s = new String(ByteArray);
double[] DoubleArray = toDouble(ByteArray);
// build 2^n array, fill up with zeroes
boolean exp = false;
int i = 0;
int pow = 0;
while (!exp) {
pow = (int) Math.pow(2, i);
if (pow > ByteArray.length) {
exp = true;
} else {
i++;
}
}
System.out.println(pow);
double[] Filledup = new double[pow];
for (int j = 0; j < DoubleArray.length; j++) {
Filledup[j] = DoubleArray[j];
System.out.println(DoubleArray[j]);
}
for (int k = DoubleArray.length; k < Filledup.length; k++) {
Filledup[k] = 0;
}
这是我用来将字节数组转换为双精度数组的函数:
public static double[] toDouble(byte[] byteArray) {
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
double[] doubles = new double[byteArray.length / 8];
for (int i = 0; i < doubles.length; i++) {
doubles[i] = byteBuffer.getDouble(i * 8);
}
return doubles;
}
header还在里面,我知道,但这应该是目前最小的问题了。我将我的字节数组转换为双精度数组,然后用零将该数组填充为 2 的下一个幂,以便 FFT 可以实际工作(它需要一个 2^n 值的数组)。我使用的 FFT 算法获取两个双数组作为输入,一个是实部,另一个是虚部。我读到,为了使它起作用,我必须将虚构数组保持为空(但它的长度与实际数组相同)。
值得一提:我正在用 44100 kHz、16 位和单声道录音。
如有必要,我将 post 我正在使用的 FFT。
如果我尝试打印双精度数组的值,我会得到一些奇怪的结果:
...
-2.0311904060823147E236
-1.3309975624948503E241
1.630738286366793E-260
1.0682002560745842E-255
-5.961832069690704E197
-1.1476447092561027E164
-1.1008407401197794E217
-8.109566204271759E298
-1.6104556241572942E265
-2.2081172620352248E130
NaN
3.643749694745671E-217
-3.9085815506127892E202
-4.0747557114875874E149
...
我知道问题出在某个地方,我忽略了一些我应该注意的非常简单的事情,但我似乎找不到问题所在。我最后的问题是:我怎样才能让它工作?
wave 文件中的示例不会已经是 8 字节的双打,可以按照您发布的代码直接复制。
在将样本转换为双精度之前,您需要查找(部分来自 WAVE header 格式和 RIFF 规范)样本的数据类型、格式、长度和字节顺序。
尝试使用 2 字节 little-endian 有符号整数作为可能的可能性。
There's a header in the way I need to get rid of […]
如果你想"skip" header,你需要使用javax.sound.sampled.AudioInputStream
来读取文件。无论如何,这对学习很有用,因为如果您事先不知道确切的格式,您将需要 header 中的数据来解释字节。
I'm recording with 44100 kHz, 16 bit and mono.
因此,这几乎可以肯定意味着文件中的数据被编码为 16 位 整数 (Java 命名法中的 short
)。
现在,您的 ByteBuffer
代码假设它已经是 64 位浮点数,这就是您得到奇怪结果的原因。换句话说,您正在 重新解释 二进制 short
数据,就好像它是 double
.
你需要做的是读入short
数据,然后转换到double
。
例如,这是您尝试执行的基本例程(支持 8、16、32 和 64 位带符号整数 PCM):
import javax.sound.sampled.*;
import javax.sound.sampled.AudioFormat.Encoding;
import java.io.*;
import java.nio.*;
static double[] readFully(File file)
throws UnsupportedAudioFileException, IOException {
AudioInputStream in = AudioSystem.getAudioInputStream(file);
AudioFormat fmt = in.getFormat();
byte[] bytes;
try {
if(fmt.getEncoding() != Encoding.PCM_SIGNED) {
throw new UnsupportedAudioFileException();
}
// read the data fully
bytes = new byte[in.available()];
in.read(bytes);
} finally {
in.close();
}
int bits = fmt.getSampleSizeInBits();
double max = Math.pow(2, bits - 1);
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.order(fmt.isBigEndian() ?
ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
double[] samples = new double[bytes.length * 8 / bits];
// convert sample-by-sample to a scale of
// -1.0 <= samples[i] < 1.0
for(int i = 0; i < samples.length; ++i) {
switch(bits) {
case 8: samples[i] = ( bb.get() / max );
break;
case 16: samples[i] = ( bb.getShort() / max );
break;
case 32: samples[i] = ( bb.getInt() / max );
break;
case 64: samples[i] = ( bb.getLong() / max );
break;
default: throw new UnsupportedAudioFileException();
}
}
return samples;
}
The FFT algorithm I'm using gets two double arrays as input, one being the real, the other being the imaginary part. I read, that for this to work, I'd have to keep the imaginary array empty (but its length being the same as the real array).
没错。 real部分是文件中的音频样本数组,imaginary部分是一个等长的数组,用0填充例如:
double[] realPart = mySamples;
double[] imagPart = new double[realPart.length];
myFft(realPart, imagPart);
我对Java不是很精通,所以请保持简单。不过,我会尝试理解您 post 的一切。这是我的问题。
我已经编写了代码来从外部麦克风录制音频并将其存储在 .wav 中。存储此文件与存档目的相关。我需要做的是对存储的音频进行 FFT。
我的方法是将 wav 文件加载为字节数组并将其转换,问题是 1. 我需要摆脱的方式有一个 header,但我应该能够做到这一点和 2. 我得到了一个字节数组,但是我在网上找到的大多数(如果不是全部)FFT 算法都试图修补到我的项目中,使用复杂的/两个双精度数组。
我尝试解决这两个问题并最终能够将我的 FFT 阵列绘制成图表,但我发现它只是返回“0”。 .wav 文件很好,我可以毫无问题地播放它。我想也许将字节转换成双精度对我来说是个问题,所以这是我的方法(我知道这不漂亮)
byte ByteArray[] = Files.readAllBytes(wav_path);
String s = new String(ByteArray);
double[] DoubleArray = toDouble(ByteArray);
// build 2^n array, fill up with zeroes
boolean exp = false;
int i = 0;
int pow = 0;
while (!exp) {
pow = (int) Math.pow(2, i);
if (pow > ByteArray.length) {
exp = true;
} else {
i++;
}
}
System.out.println(pow);
double[] Filledup = new double[pow];
for (int j = 0; j < DoubleArray.length; j++) {
Filledup[j] = DoubleArray[j];
System.out.println(DoubleArray[j]);
}
for (int k = DoubleArray.length; k < Filledup.length; k++) {
Filledup[k] = 0;
}
这是我用来将字节数组转换为双精度数组的函数:
public static double[] toDouble(byte[] byteArray) {
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
double[] doubles = new double[byteArray.length / 8];
for (int i = 0; i < doubles.length; i++) {
doubles[i] = byteBuffer.getDouble(i * 8);
}
return doubles;
}
header还在里面,我知道,但这应该是目前最小的问题了。我将我的字节数组转换为双精度数组,然后用零将该数组填充为 2 的下一个幂,以便 FFT 可以实际工作(它需要一个 2^n 值的数组)。我使用的 FFT 算法获取两个双数组作为输入,一个是实部,另一个是虚部。我读到,为了使它起作用,我必须将虚构数组保持为空(但它的长度与实际数组相同)。
值得一提:我正在用 44100 kHz、16 位和单声道录音。
如有必要,我将 post 我正在使用的 FFT。
如果我尝试打印双精度数组的值,我会得到一些奇怪的结果:
...
-2.0311904060823147E236
-1.3309975624948503E241
1.630738286366793E-260
1.0682002560745842E-255
-5.961832069690704E197
-1.1476447092561027E164
-1.1008407401197794E217
-8.109566204271759E298
-1.6104556241572942E265
-2.2081172620352248E130
NaN
3.643749694745671E-217
-3.9085815506127892E202
-4.0747557114875874E149
...
我知道问题出在某个地方,我忽略了一些我应该注意的非常简单的事情,但我似乎找不到问题所在。我最后的问题是:我怎样才能让它工作?
wave 文件中的示例不会已经是 8 字节的双打,可以按照您发布的代码直接复制。
在将样本转换为双精度之前,您需要查找(部分来自 WAVE header 格式和 RIFF 规范)样本的数据类型、格式、长度和字节顺序。
尝试使用 2 字节 little-endian 有符号整数作为可能的可能性。
There's a header in the way I need to get rid of […]
如果你想"skip" header,你需要使用javax.sound.sampled.AudioInputStream
来读取文件。无论如何,这对学习很有用,因为如果您事先不知道确切的格式,您将需要 header 中的数据来解释字节。
I'm recording with 44100 kHz, 16 bit and mono.
因此,这几乎可以肯定意味着文件中的数据被编码为 16 位 整数 (Java 命名法中的 short
)。
现在,您的 ByteBuffer
代码假设它已经是 64 位浮点数,这就是您得到奇怪结果的原因。换句话说,您正在 重新解释 二进制 short
数据,就好像它是 double
.
你需要做的是读入short
数据,然后转换到double
。
例如,这是您尝试执行的基本例程(支持 8、16、32 和 64 位带符号整数 PCM):
import javax.sound.sampled.*;
import javax.sound.sampled.AudioFormat.Encoding;
import java.io.*;
import java.nio.*;
static double[] readFully(File file)
throws UnsupportedAudioFileException, IOException {
AudioInputStream in = AudioSystem.getAudioInputStream(file);
AudioFormat fmt = in.getFormat();
byte[] bytes;
try {
if(fmt.getEncoding() != Encoding.PCM_SIGNED) {
throw new UnsupportedAudioFileException();
}
// read the data fully
bytes = new byte[in.available()];
in.read(bytes);
} finally {
in.close();
}
int bits = fmt.getSampleSizeInBits();
double max = Math.pow(2, bits - 1);
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.order(fmt.isBigEndian() ?
ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
double[] samples = new double[bytes.length * 8 / bits];
// convert sample-by-sample to a scale of
// -1.0 <= samples[i] < 1.0
for(int i = 0; i < samples.length; ++i) {
switch(bits) {
case 8: samples[i] = ( bb.get() / max );
break;
case 16: samples[i] = ( bb.getShort() / max );
break;
case 32: samples[i] = ( bb.getInt() / max );
break;
case 64: samples[i] = ( bb.getLong() / max );
break;
default: throw new UnsupportedAudioFileException();
}
}
return samples;
}
The FFT algorithm I'm using gets two double arrays as input, one being the real, the other being the imaginary part. I read, that for this to work, I'd have to keep the imaginary array empty (but its length being the same as the real array).
没错。 real部分是文件中的音频样本数组,imaginary部分是一个等长的数组,用0填充例如:
double[] realPart = mySamples;
double[] imagPart = new double[realPart.length];
myFft(realPart, imagPart);