签名的 16 位 PCM 转换不起作用。为啥?

Posted

技术标签:

【中文标题】签名的 16 位 PCM 转换不起作用。为啥?【英文标题】:Signed 16 bit PCM transformations aren't working. Why?签名的 16 位 PCM 转换不起作用。为什么? 【发布时间】:2013-09-11 14:49:53 【问题描述】:

在过去的 2 天里,我一直在尝试在 android 上操作 16 位 PCM 数据,但收效甚微。我目前正在使用WAV recorder 来捕获音频。在使用randomAccessWriter 写入缓冲区之前的onPeriodicNotification(AudioRecord recorder) 方法中,我将缓冲区发送到自定义类,以操作样本,并将样本保存回缓冲区。我的自定义类中的方法如下:

由于缓冲区是一个字节数组,我首先将它们转换为短片,现在一个短片代表一帧(只有一个通道)。一旦我克服了这个障碍,我将实现 FFT 算法,这需要输入是一个浮点数组 - 所以我将每个短路转换为浮点数。现在,将数据写入 WAV 文件的randomAccessWriter 接受一个字节数组,并期望每帧为 2 个字节。因此,我将每个浮点数转换回一个短整数,并使用 ByteBuffer 重建一个字节数组,然后返回该数组。当我运行我的记录器应用程序时,通过上述代码发送缓冲区,一切都很好。

我尝试使用简单的语音调制算法来测试录音是否被修改,算法放在TODO注释所在的位置:

现在,如果我在我的 iPhone 上使用上述代码,音频样本将被转换,尽管数据本身是 32 位浮点数。但是,在 Android 上,当我重新运行记录器应用程序并插入上述代码时,所产生的只是白噪声。在我可以使用上述代码成功修改示例之前,我无法继续使用我的 FFT 算法。

为什么会这样?如果有关于该主题的知识的人可以阐明该主题,我将不胜感激。

已解决 - Bjorn Roche

根本原因:录制是在小端中提供数据,而 Java 短片是在大端中;当使用两种不同形式应用函数时,会产生白噪声。下面的代码展示了如何接收一个 Little Endian 字节数组,转换为 Big Endian 浮点数组并返回到 Little Endian 字节数组。虽然浮动你可以随心所欲,但我现在将使用我的 FFT 算法:

public byte[] manipulateSamples(byte[] data,
                                int samplingRate,
                                int numFrames,
                                short numChannels) 

    // Convert byte[] to short[] (16 bit) to float[] (32 bit) (End result: Big Endian)
    ShortBuffer sbuf = ByteBuffer.wrap(data).asShortBuffer();
    short[] audioshorts = new short[sbuf.capacity()];
    sbuf.get(audioShorts);

    float[] audioFloats = new float[audioShorts.length];

    for (int i = 0; i < audioShorts.length; i++) 
        audioFloats[i] = ((float)Short.reverseBytes(audioShorts[i])/0x8000);
    

    // Do your tasks here.

    // Convert float[] to short[] to byte[] (End result: Little Endian)
    audioShorts = new short[audioFloats.length];
    for (int i = 0; i < audioFloats.length; i++) 
        audioShorts[i] = Short.reverseBytes((short) ((audioFloats[i])*0x8000));
    

    byte byteArray[] = new byte[audioShorts.length * 2];
    ByteBuffer buffer = ByteBuffer.wrap(byteArray);
    sbuf = buffer.asShortBuffer();
    sbuf.put(audioShorts);
    data = buffer.array();

    return data;


【问题讨论】:

首先,我会检查传递场景,中间没有任何处理。你应该把你放进去的东西拿出来。我可以建议你写一些单元测试吗?您也可以尝试使用调试器逐步完成此操作。我会特别注意将浮点数除以整数 ((float)audioShorts[i])/0x8000)。不记得 Java 的促销规则,但这看起来是一个合理的原因。尝试将其重铸为(float)audioShorts[i])/32768.0f。你可能在另一个方向上也有同样的问题。 感谢您的输入,我试过中间不做任何处理,数据通过正常,我也试过你的建议,噪音仍然存在,我很困惑为什么会这样因为我不知道为什么会发生,所以我无法编写任何 junit 测试。 在尝试如此复杂的测试之前,您应该尝试一些简单的事情,例如除以 2。 我也试过了,而不是 Math.sin(theta) 我做了 *= 0.5。结果是白噪声。 【参考方案1】:

您的问题是 java 中的 short 是 bigendian,但如果您从 WAV 文件中获取数据,则数据是 little endian。

【讨论】:

无论如何我可以将它们转换为 Little Endian,将样本转换为 Big Endian(正如 RandomAccessWriter 所期望的那样),还是我必须保存 wav 文件,然后重新打开它然后对样本做我想做的事情? 等等,我刚刚重读了你说的话。数据已经在 Big Endian 中,正在从 Mic 记录数据。因此,我不应该能够以我尝试的方式进行转换吗? 我可能会错过它,但是查看 wav 记录器的代码,我看不到记录和写入文件之间的任何字节交换。也就是说录音的字节序和文件是一样的,而WAV文件是little-endian,所以录音是little endian。 为什么不尝试交换字节,除以二,然后将字节交换回来。你会知道它是否有效。 我非常感谢你,我的困惑来自使用 ByteBuffers 的 order() 函数以及使用 Byte.BIG_ENDIAN 和 Byte.LITTLE_ENDIAN 但这些都失败了。相反,我把单条短裤倒过来,它奏效了。谢谢!!!

以上是关于签名的 16 位 PCM 转换不起作用。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

短到 Int 类型转换不起作用,为啥?

使用 add + shift 将 pcm16 转换为 pcm14

将音频转换为 8 位签名 PCM

PYTHON:简单的浮点转换不起作用,为啥?

为啥转换不起作用?

为啥我的 JavaScript RGB Hex 转换器不起作用?