【问题标题】:Converting Byte Array to Double Array将字节数组转换为双精度数组
【发布时间】:2013-03-10 03:44:23
【问题描述】:

我在使用 Java 中的 WAV 文件时遇到了一些问题。

WAV 格式:PCM_SIGNED 44100.0 Hz,24 位,立体声,6 字节/帧,little-endian。

  • 我将 WAV 数据提取到一个字节数组中,没有任何问题。
  • 我正在尝试将字节数组转换为双精度数组,但有些双精度值带有 NaN 值。

代码:

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);
}

16/24/32 位单声道/立体声的事实让我感到困惑。

我打算将double[] 传递给 FFT 算法并获取音频频率。

【问题讨论】:

  • 听起来有些数字实际上不能解释为double...
  • @maszter:不是重复的,因为字节不代表双精度。
  • @Leandro T 如果您对答案感到满意,请接受它,或者发表评论为什么认为应该改进答案。

标签: java arrays floating-point fft wav


【解决方案1】:

试试这个:

public static byte[] toByteArray(double[] doubleArray){
    int times = Double.SIZE / Byte.SIZE;
    byte[] bytes = new byte[doubleArray.length * times];
    for(int i=0;i<doubleArray.length;i++){
        ByteBuffer.wrap(bytes, i*times, times).putDouble(doubleArray[i]);
    }
    return bytes;
}

public static double[] toDoubleArray(byte[] byteArray){
    int times = Double.SIZE / Byte.SIZE;
    double[] doubles = new double[byteArray.length / times];
    for(int i=0;i<doubles.length;i++){
        doubles[i] = ByteBuffer.wrap(byteArray, i*times, times).getDouble();
    }
    return doubles;
}

public static byte[] toByteArray(int[] intArray){
    int times = Integer.SIZE / Byte.SIZE;
    byte[] bytes = new byte[intArray.length * times];
    for(int i=0;i<intArray.length;i++){
        ByteBuffer.wrap(bytes, i*times, times).putInt(intArray[i]);
    }
    return bytes;
}

public static int[] toIntArray(byte[] byteArray){
    int times = Integer.SIZE / Byte.SIZE;
    int[] ints = new int[byteArray.length / times];
    for(int i=0;i<ints.length;i++){
        ints[i] = ByteBuffer.wrap(byteArray, i*times, times).getInt();
    }
    return ints;
}

【讨论】:

    【解决方案2】:

    您的 WAV 格式是 24 位,但双精度使用 64 位。因此,存储在您的 wav 中的数量不能翻倍。每个帧和通道有一个 24 位有符号整数,相当于提到的这 6 个字节。

    你可以这样做:

    private static double readDouble(ByteBuffer buf) {
      int v = (byteBuffer.get() & 0xff);
      v |= (byteBuffer.get() & 0xff) << 8;
      v |= byteBuffer.get() << 16;
      return (double)v;
    }
    

    您会为左通道调用一次该方法,然后为右通道调用一次。不确定正确的顺序,但我想先离开。正如 little-endian 所示,字节从最低有效位读取到最高有效位。较低的两个字节用0xff 屏蔽,以便将它们视为无符号。最高有效字节被视为有符号,因为它将包含有符号 24 位整数的符号。

    如果您对数组进行操作,则可以不使用ByteBuffer,例如像这样:

    double[] doubles = new double[byteArray.length / 3];
    for (int i = 0, j = 0; i != doubles.length; ++i, j += 3) {
      doubles[i] = (double)( (byteArray[j  ] & 0xff) | 
                            ((byteArray[j+1] & 0xff) <<  8) |
                            ( byteArray[j+2]         << 16));
    }
    

    您将获得交错的两个通道的样本,因此您可能希望之后将它们分开。

    如果您有单声道,则不会有两个通道交错,而只有一次。对于 16 位,您可以使用 byteBuffer.getShort(),对于 32 位,您可以使用 byteBuffer.getInt()。但是 24 位不常用于计算,所以ByteBuffer 没有方法。如果您有未签名的样本,则必须屏蔽所有符号并抵消结果,但我猜未签名的 WAV 相当不常见。

    【讨论】:

    • 谢谢,我去看看!
    【解决方案3】:

    对于 DSP 中的浮点类型,它们通常更喜欢 [0, 1] 或 [0, 1) 范围内的值,因此您应该将每个元素除以 224-1。喜欢上面 MvG 的答案,但有一些变化

    int t = ((byteArray[j  ] & 0xff) <<  0) |
            ((byteArray[j+1] & 0xff) <<  8) |
             (byteArray[j+2]         << 16);
    return t/double(0xFFFFFF);
    

    但是double对于数据处理来说确实是浪费空间和CPU。我建议将其转换为 32 位 int,或 float,它具有相同的精度(24 位)但范围更大。事实上,当您进行音频或视频处理时,32 位 int 或 float 是数据通道的最大类型

    最后你可以利用多线程和SIMD 来加速转换

    【讨论】:

    • 您的代码看起来有点混乱。您在循环内分配给t,而不是doubles。所以最后你将拥有最后一个样本的整数值,存储在t 中。你划分,但double(0xFFFFFF) 不是有效的Java; (double)0xFFFFFF 是。
    • @MvG 抱歉,我的意思是在一个循环中。已编辑
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-02
    • 2010-11-21
    • 2011-07-07
    • 2012-03-07
    • 2015-10-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多