将字节数组转换为双精度数组

5

我在Java中遇到了一些WAV文件问题。

WAV格式:PCM_SIGNED 44100.0 Hz,24位,立体声,每帧6字节,小端字节序。

  • 我已成功将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”类型... - Louis Wasserman
重复的问题,参见https://dev59.com/43E85IYBdhLWcg3wLAUV? - maszter
@maszter:不是重复,因为这些字节不代表双精度。 - MvG
@Leandro T 如果您对答案满意,请接受它,或留下评论说明为什么认为答案应该改进。 - Vishrant
3个回答

14

试试这个:

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

4

你的WAV格式是24位,但double使用64位。因此,存储在你的wav中的数量不能是double类型的。每个帧和通道都有一个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不太常见。


0

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或者具有相同精度(24位)但范围更大的float。实际上,在音频或视频处理时,32位int或float是数据通道的最大类型。

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


你的代码看起来有点混乱。你在循环内部赋值给了t,但没有赋值给doubles。所以最终你只会得到最后一个样本的整数值,存储在t中。你进行了除法运算,但是double(0xFFFFFF)不是有效的Java语法;(double)0xFFFFFF才是。 - MvG
@MvG 抱歉,我的意思是在一个循环中完成。已编辑。 - phuclv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接