【问题标题】:Fourier transforming a byte array傅里叶变换字节数组
【发布时间】:2015-06-16 02:48:32
【问题描述】:

我对Java不是很精通,所以请保持简单。不过,我会尝试理解您发布的所有内容。这是我的问题。

我已经编写了从外部麦克风录制音频并将其存储在 .wav 中的代码。存储此文件与归档目的相关。我需要做的是存储音频的 FFT。

我的方法是将 wav 文件加载为字节数组并对其进行转换,但问题是 1. 我需要摆脱一个标题,但我应该能够做到这一点,并且 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;
}

标题仍然在那里,我知道,但这应该是目前最小的问题。我将我的字节数组转换为一个双精度数组,然后用零将该数组填充到 2 的下一个幂,以便 FFT 可以实际工作(它需要一个 2^n 值的数组)。我正在使用的 FFT 算法将两个双精度数组作为输入,一个是实数,另一个是虚数部分。我读到,为了让它工作,我必须保持虚数组为空(但它的长度与实际数组相同)。

值得一提:我正在使用 44100 kHz、16 位和单声道录制。

如有必要,我会发布我正在使用的 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
...

我知道问题在于我忽略了一些我应该注意的非常简单的事情,但我似乎找不到问题所在。最后我的问题是:我怎样才能让它工作?

【问题讨论】:

  • 问题是如何将字节值转换为双精度值?这部分代码没有显示。你用docs.oracle.com/javase/8/docs/api/java/lang/… 吗?
  • 我会在上面的代码中包含这个函数。
  • 您说的是标头,它是字节数组的一部分吗?如果是这种情况,您必须在读取双精度数据之前跳过此标头的 nb 个字节。
  • 源数组不是“复杂的”。然而,大多数算法会产生一个“复杂”的输出,其中包括“真实”和“虚构”时域数据。通过取平方和的平方根(或简单地将平方和视为“幂”值)将实数和虚数组合成一个“幅度”数是很常见的。您得到的频率“桶”数量是您输入的时域值数量的一半——这是由于“奈奎斯特频率”。
  • 它仍然是字节数组的一部分,它应该是前 44 个字节。我可以立即摆脱它,但它不会显着影响 FFT。

标签: java arrays audio byte fft


【解决方案1】:

我需要删除 […]

如果你想“跳过”标题,你需要使用javax.sound.sampled.AudioInputStream 来读取文件。无论如何,这对学习很有用,因为如果您事先不知道确切的格式,则需要标头中的数据来解释字节。

我正在使用 44100 kHz、16 位和单声道录音。

因此,这几乎可以肯定意味着文件中的数据被编码为 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;
}

我使用的 FFT 算法将两个双精度数组作为输入,一个是实数,另一个是虚数部分。我读到,要让它工作,我必须保持虚数组为空(但它的长度与实际数组相同)。

没错。 real 部分是文件中的音频样本数组,imaginary 部分是等长数组,用 0 填充,例如:

double[] realPart = mySamples;
double[] imagPart = new double[realPart.length];
myFft(realPart, imagPart);

更多信息..."How do I use audio sample data from Java Sound?"

【讨论】:

  • 这对于理解 wav 文件的读取过程非常有帮助,谢谢。
【解决方案2】:

wave 文件中的样本不会已经是 8 字节双精度,可以根据您发布的代码直接复制。

在将样本转换为双精度之前,您需要查找(部分来自 WAVE 标头格式和 RIFF 规范)样本的数据类型、格式、长度和字节序。

尝试 2 字节 little-endian 有符号整数作为可能的可能性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-12
    • 2011-07-12
    • 1970-01-01
    • 2012-05-18
    • 2021-05-18
    相关资源
    最近更新 更多